Compare commits
107 Commits
v1.84.0
...
rf/profile
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8f617f80b | ||
|
|
e2f0584cea | ||
|
|
e1712e63a6 | ||
|
|
2031e1ebd0 | ||
|
|
de8dc1e9cd | ||
|
|
de87d7ac27 | ||
|
|
2b003c684f | ||
|
|
bdf717c7b5 | ||
|
|
3097510550 | ||
|
|
0c0b2d88cc | ||
|
|
1ffed41cf9 | ||
|
|
dac61d1c98 | ||
|
|
facb67093c | ||
|
|
341a9662b7 | ||
|
|
e80454e7fd | ||
|
|
3a232dbb57 | ||
|
|
ee45f1ca7b | ||
|
|
bbb6ee978d | ||
|
|
5961995e80 | ||
|
|
78f5fd275e | ||
|
|
a7c4c5d0a8 | ||
|
|
617220d75a | ||
|
|
09042583c7 | ||
|
|
2e80404e5e | ||
|
|
8ef29099f8 | ||
|
|
1097dccfe5 | ||
|
|
76a2a1db36 | ||
|
|
65721b3b20 | ||
|
|
7675f08b7b | ||
|
|
b962ae3578 | ||
|
|
34a8b01b76 | ||
|
|
179382afbd | ||
|
|
37ee631363 | ||
|
|
dba37c2771 | ||
|
|
33f2bad8d9 | ||
|
|
368cdefd91 | ||
|
|
266b5b00da | ||
|
|
8fe68c832b | ||
|
|
92be102a07 | ||
|
|
a344928f25 | ||
|
|
f214d5f96b | ||
|
|
4af39f081b | ||
|
|
3a6b655ba8 | ||
|
|
2f156d09bf | ||
|
|
b84be60c53 | ||
|
|
eef9017a05 | ||
|
|
eff61bb8d3 | ||
|
|
8a594a89ad | ||
|
|
fb60768cf3 | ||
|
|
8f7a11b896 | ||
|
|
0b4da1a97c | ||
|
|
f6d14f7fc3 | ||
|
|
449e7de71a | ||
|
|
95ed99a1d2 | ||
|
|
8c72722710 | ||
|
|
17176bb8d1 | ||
|
|
4fb7468cf3 | ||
|
|
51fc436456 | ||
|
|
7f9050b285 | ||
|
|
94eecea02b | ||
|
|
4ec035b09a | ||
|
|
922682c4d0 | ||
|
|
831ff60bdf | ||
|
|
b86ca29fde | ||
|
|
15c75d9d00 | ||
|
|
096bf2022c | ||
|
|
bc4dd0eeaa | ||
|
|
ae219eb3be | ||
|
|
b851a5c65a | ||
|
|
bc36f5b309 | ||
|
|
5b0a4d7838 | ||
|
|
f358aa5fe2 | ||
|
|
3e5ff8682a | ||
|
|
0cbefd8214 | ||
|
|
517b2c9cca | ||
|
|
7a9091fed6 | ||
|
|
2819b09ce5 | ||
|
|
ef0165e419 | ||
|
|
be97be2c58 | ||
|
|
daf827666b | ||
|
|
8c53598aba | ||
|
|
6f33d549f0 | ||
|
|
390a988d4c | ||
|
|
8a8316c316 | ||
|
|
c638c511ca | ||
|
|
59403fbe5d | ||
|
|
7eed0b4666 | ||
|
|
4127ffe00c | ||
|
|
3d9dfa645b | ||
|
|
7c0de93b3d | ||
|
|
de1e29492c | ||
|
|
0b8a08cb49 | ||
|
|
4f7c45118b | ||
|
|
cdbab5c807 | ||
|
|
d4927cf757 | ||
|
|
492f22526a | ||
|
|
89c2fb41dd | ||
|
|
018b051781 | ||
|
|
c19be7a2fa | ||
|
|
aa3a3f6612 | ||
|
|
5b8c6bb35d | ||
|
|
8d487c0ddb | ||
|
|
efea19496f | ||
|
|
ab99950c5d | ||
|
|
2062dc6c44 | ||
|
|
b6d5eef547 | ||
|
|
46d2c86b37 |
7
.env
7
.env
@@ -1,2 +1,7 @@
|
||||
DB_PASSWORD=changeme
|
||||
WM_BASE_URL=localhost
|
||||
|
||||
# this is the url that your instance is publicly exposed to
|
||||
WM_BASE_URL=http://localhost
|
||||
|
||||
# this is the url that caddy will reverse proxy from. It might be different than WM_BASE_URL if you re using a second proxy/load balancer in front
|
||||
CADDY_REVERSE_PROXY=http://localhost
|
||||
|
||||
32
.github/workflows/deploy-to-s3.yml
vendored
32
.github/workflows/deploy-to-s3.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Deploy frontend to s3
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["*"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-npmbuild
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
npm_build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: "npm build"
|
||||
timeout-minutes: 15
|
||||
run:
|
||||
cd frontend && npm ci && npm run generate-backend-client &&
|
||||
NODE_OPTIONS="--max-old-space-size=8192" npm run build
|
||||
|
||||
- uses: reggionick/s3-deploy@v3
|
||||
with:
|
||||
folder: frontend/build
|
||||
bucket: windmill-frontend
|
||||
bucket-region: us-east-1
|
||||
45
.github/workflows/docker-image.yml
vendored
45
.github/workflows/docker-image.yml
vendored
@@ -18,7 +18,7 @@ permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -50,7 +50,6 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
@@ -63,7 +62,7 @@ jobs:
|
||||
labels: |
|
||||
${{ steps.meta-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=AGPLv3
|
||||
|
||||
|
||||
build_ee:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
@@ -142,7 +141,28 @@ jobs:
|
||||
# run: docker kill ${{ steps.docker-container.outputs.id }}
|
||||
# if: always()
|
||||
|
||||
deploy_s3:
|
||||
needs: [build_ee]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: shrink/actions-docker-extract@v2
|
||||
id: extract
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:latest
|
||||
path: "/static_frontend/."
|
||||
|
||||
- uses: reggionick/s3-deploy@v3
|
||||
with:
|
||||
folder: ${{ steps.extract.outputs.destination }}
|
||||
bucket: windmill-frontend
|
||||
bucket-region: us-east-1
|
||||
publish_privately_heavy:
|
||||
needs: [build_ee]
|
||||
runs-on: [self-hosted, new]
|
||||
@@ -191,8 +211,12 @@ jobs:
|
||||
tags: |
|
||||
${{ steps.meta-heavy.outputs.tags }}
|
||||
labels: ${{ steps.meta-heavy.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-heavy:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-heavy:buildcache,mode=max
|
||||
cache-from:
|
||||
type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME
|
||||
}}-heavy:buildcache
|
||||
cache-to:
|
||||
type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME
|
||||
}}-heavy:buildcache,mode=max
|
||||
|
||||
publish_privately_helm:
|
||||
runs-on: [self-hosted, new]
|
||||
@@ -204,7 +228,6 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
@@ -220,7 +243,7 @@ jobs:
|
||||
registry: ${{ env.ECR_REGISTRY }}
|
||||
username: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
|
||||
- name: Build and push privately
|
||||
uses: docker/build-push-action@v4
|
||||
if: github.event_name != 'pull_request'
|
||||
@@ -230,5 +253,9 @@ jobs:
|
||||
file: ./docker/DockerfileHelm
|
||||
tags: |
|
||||
${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}:helm
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-helm:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-helm:buildcache,mode=max
|
||||
cache-from:
|
||||
type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME
|
||||
}}-helm:buildcache
|
||||
cache-to:
|
||||
type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME
|
||||
}}-helm:buildcache,mode=max
|
||||
|
||||
2
.github/workflows/go_on_release.yml
vendored
2
.github/workflows/go_on_release.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- name: generate_go
|
||||
run: |
|
||||
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.11.0
|
||||
|
||||
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,6 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.86.0](https://github.com/windmill-labs/windmill/compare/v1.85.0...v1.86.0) (2023-04-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backend:** add /ready endpoint for workers ([94eecea](https://github.com/windmill-labs/windmill/commit/94eecea02b6295ad5674db4b010bf6ab7984fa17))
|
||||
* **backend:** add GET endpoint to trigger scripts ([15c75d9](https://github.com/windmill-labs/windmill/commit/15c75d9d00a69ae97123ed371b9657e298345bdb))
|
||||
* **backend:** lowercase all emails in relevant endpoints ([#1361](https://github.com/windmill-labs/windmill/issues/1361)) ([7f9050b](https://github.com/windmill-labs/windmill/commit/7f9050b285cf8f7f6baf05452b673f58988c452c))
|
||||
* **cli:** add getFullResource ([3a232db](https://github.com/windmill-labs/windmill/commit/3a232dbb5792c28b26747e1ba260fffcdd4a8416))
|
||||
* do cache bucket syncing in background + check tar before pushing it ([#1360](https://github.com/windmill-labs/windmill/issues/1360)) ([3e5ff86](https://github.com/windmill-labs/windmill/commit/3e5ff8682a298ba9e59b2662c4c04c5698447204))
|
||||
* **frontend:** add flow expand button ([34a8b01](https://github.com/windmill-labs/windmill/commit/34a8b01b762c0b210d76101e7da7bd2397258e8d))
|
||||
* **frontend:** add impersonate api + local resolution of import by lsp v0 ([7675f08](https://github.com/windmill-labs/windmill/commit/7675f08b7bfe319e496a86a7ef1ab7cc8c1d12d2))
|
||||
* **frontend:** add workspace to ctx ([8f7a11b](https://github.com/windmill-labs/windmill/commit/8f7a11b8964e2c3405ce3689f9cf2298f9e71c75))
|
||||
* **frontend:** Improve login + toasts ([#1363](https://github.com/windmill-labs/windmill/issues/1363)) ([92be102](https://github.com/windmill-labs/windmill/commit/92be102a070b1f17b9d3e40524cd21b54301b5a7))
|
||||
* **frontend:** make script editor a single page ([b84be60](https://github.com/windmill-labs/windmill/commit/b84be60c53ca1ef65826123f39099d33c1f549c0))
|
||||
* **frontend:** Tone down text + display whole text ([#1366](https://github.com/windmill-labs/windmill/issues/1366)) ([f214d5f](https://github.com/windmill-labs/windmill/commit/f214d5f96b6ac26cd3ef90a6ab696a6dfe02b3f0))
|
||||
* improved cron/schedule editor ([#1362](https://github.com/windmill-labs/windmill/issues/1362)) ([17176bb](https://github.com/windmill-labs/windmill/commit/17176bb8d112b35228ce9183f4b2f81abe9e5b6e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** allow cors ([8a594a8](https://github.com/windmill-labs/windmill/commit/8a594a89adba9915508884f900f58c4ab53cdfec))
|
||||
* **backend:** allow longer name/company ([eff61bb](https://github.com/windmill-labs/windmill/commit/eff61bb8d3496bc1c5be4b1051f99ed4470a47ff))
|
||||
* **backend:** always flush bash output ([517b2c9](https://github.com/windmill-labs/windmill/commit/517b2c9cca54628c8ee692d65c05bc2513eaaf22))
|
||||
* **backend:** always flush bash output ([7a9091f](https://github.com/windmill-labs/windmill/commit/7a9091fed6aa99201b75bab88d4faddbe041eee4))
|
||||
* **backend:** inline script app python fix ([8c72722](https://github.com/windmill-labs/windmill/commit/8c72722710db8e3720b01180b504cbc66e79f5ca))
|
||||
* **frontend:** Add FlowGraph display on Safari ([#1351](https://github.com/windmill-labs/windmill/issues/1351)) ([2819b09](https://github.com/windmill-labs/windmill/commit/2819b09ce5011a467e994ee8b1f09cf33145003d))
|
||||
* **frontend:** Fix button poppup ([#1368](https://github.com/windmill-labs/windmill/issues/1368)) ([a344928](https://github.com/windmill-labs/windmill/commit/a344928f251d697f53e40c517b0b86bd90e0ad52))
|
||||
* **frontend:** Fix connected property ([#1371](https://github.com/windmill-labs/windmill/issues/1371)) ([4af39f0](https://github.com/windmill-labs/windmill/commit/4af39f081bf3d07aaade39e5a5a221741fe8f973))
|
||||
* **frontend:** Fix flow templateEditor ([#1367](https://github.com/windmill-labs/windmill/issues/1367)) ([51fc436](https://github.com/windmill-labs/windmill/commit/51fc436456104c2d6a3cd6f6d62f08929e40d450))
|
||||
* **frontend:** make croninput a builder rather than a tab ([266b5b0](https://github.com/windmill-labs/windmill/commit/266b5b00da3bd7643eaa5dba1b8c1456f11c5e30))
|
||||
* **frontend:** Minor fixes ([#1374](https://github.com/windmill-labs/windmill/issues/1374)) ([76a2a1d](https://github.com/windmill-labs/windmill/commit/76a2a1db363facbaf9a0e9618f169d6cc66e946f))
|
||||
* no need to map internal ports to hosts ([#1365](https://github.com/windmill-labs/windmill/issues/1365)) ([4ec035b](https://github.com/windmill-labs/windmill/commit/4ec035b09a58f8859bc576b03c24cc73f335f32d))
|
||||
|
||||
## [1.85.0](https://github.com/windmill-labs/windmill/compare/v1.84.1...v1.85.0) (2023-04-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add local cache for folder path used + invalidate cache on folder creation ([018b051](https://github.com/windmill-labs/windmill/commit/018b051781e3f40b9d1da8ccdd5edb1cd49877ba))
|
||||
* **frontend:** add agGrid api hooks + ready ([de1e294](https://github.com/windmill-labs/windmill/commit/de1e29492c9aefdfc59f605ba81f7c51a96bf2f3))
|
||||
* **frontend:** Add ID renaming popup ([#1344](https://github.com/windmill-labs/windmill/issues/1344)) ([0b8a08c](https://github.com/windmill-labs/windmill/commit/0b8a08cb49644da7c354c3631751e925ac5353b9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** improve handling subflow with many depth using tailrec ([8c53598](https://github.com/windmill-labs/windmill/commit/8c53598aba3fb89f4174d1c0ab3912096ac07c96))
|
||||
* **backend:** improve subflow processing ([390a988](https://github.com/windmill-labs/windmill/commit/390a988d4c96256a4fbd6a9302fc47a5648c2c43))
|
||||
* **frontend:** PDF reader header positioning ([#1350](https://github.com/windmill-labs/windmill/issues/1350)) ([daf8276](https://github.com/windmill-labs/windmill/commit/daf827666b13917f8c9abeab5bb2b072bd0fef0b))
|
||||
|
||||
## [1.84.1](https://github.com/windmill-labs/windmill/compare/v1.84.0...v1.84.1) (2023-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** overwrite instead of smart diff ([b6d5eef](https://github.com/windmill-labs/windmill/commit/b6d5eef5479e38cc36af2db67d4c45f78c622b9a))
|
||||
|
||||
## [1.84.0](https://github.com/windmill-labs/windmill/compare/v1.83.1...v1.84.0) (2023-03-31)
|
||||
|
||||
|
||||
|
||||
14
Caddyfile
14
Caddyfile
@@ -1,15 +1,5 @@
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
http://{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* http://lsp:3001
|
||||
reverse_proxy /* http://windmill_server:8000
|
||||
|
||||
https://{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* http://lsp:3001
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,7 @@ ENV TZ=Etc/UTC
|
||||
|
||||
RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
|
||||
COPY --from=frontend /frontend/build /static_frontend
|
||||
COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
|
||||
|
||||
COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
11
README.md
11
README.md
@@ -186,14 +186,19 @@ compiling from source or using without a postgres super user, see
|
||||
|
||||
### Docker compose
|
||||
|
||||
`docker compose up` with the following docker-compose is sufficient:
|
||||
<https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml>
|
||||
```
|
||||
curl https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml -o docker-compose.yml
|
||||
curl https://github.com/windmill-labs/windmill/blob/main/CaddyFile -o Caddyfile
|
||||
curl https://github.com/windmill-labs/windmill/blob/main/.env -o .env
|
||||
|
||||
docker compose up -d --pull always
|
||||
```
|
||||
|
||||
Go to http://localhost et voilà :)
|
||||
|
||||
The default super-admin user is: admin@windmill.dev / changeme
|
||||
|
||||
From there, you can create other users (do not forget to change the password!)
|
||||
From there, you can follow the setup app and creat other users.
|
||||
|
||||
### Kubernetes (k8s) and Helm charts
|
||||
|
||||
|
||||
383
backend/Cargo.lock
generated
383
backend/Cargo.lock
generated
@@ -36,7 +36,7 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.9",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
@@ -214,7 +214,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -259,7 +259,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -537,6 +537,28 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@@ -579,7 +601,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -615,9 +637,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
|
||||
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
@@ -678,9 +700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
@@ -794,7 +816,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -811,7 +833,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -863,9 +885,9 @@ checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e"
|
||||
|
||||
[[package]]
|
||||
name = "deno_core"
|
||||
version = "0.177.0"
|
||||
version = "0.179.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1563a04f319904eb5c4c981b0a5475a4258aa72d4576296f097a4f23b20f8428"
|
||||
checksum = "8c9307ca2299cb7b0bdaa345cbdc82a252a8e4e5a4463e28f44c715d55e460fb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -888,9 +910,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deno_ops"
|
||||
version = "0.55.0"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "769968c69c652009e43cb9e63f610a1cc3d0316c3fc59d7327f51089f65a70f4"
|
||||
checksum = "04610f07342fbb33a2b7ea7aa16a95ab71adb13a0ce858a8d1a1414660a83e3e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pmutil",
|
||||
@@ -1069,13 +1091,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
|
||||
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1105,14 +1127,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.20"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
|
||||
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1244,9 +1266,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.12.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
@@ -1265,7 +1287,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1330,9 +1352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -1374,6 +1396,16 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gosyn"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1584c4cbcb6f1d97481c19d1951c64f6e1256e5a03c28159ae45431c00511ee7"
|
||||
dependencies = [
|
||||
"strum",
|
||||
"unic-ucd-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.16"
|
||||
@@ -1602,9 +1634,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.54"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d"
|
||||
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -1674,13 +1706,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.9"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1704,14 +1736,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.6"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8"
|
||||
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1878,9 +1910,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
version = "0.2.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
@@ -1992,6 +2024,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.0"
|
||||
@@ -2120,6 +2158,7 @@ dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -2215,9 +2254,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.48"
|
||||
version = "0.10.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
|
||||
checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -2230,13 +2269,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2247,11 +2286,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.83"
|
||||
version = "0.9.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
|
||||
checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
@@ -2266,9 +2304,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
@@ -2318,6 +2356,15 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
@@ -2548,9 +2595,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.54"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2558,7 +2605,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e04bb171f6b0016b4d1ec4e1ebb42f07277d2be2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#3a5fe998e6c790223a9ff195df96e1f34ba2c149"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"built",
|
||||
@@ -2576,7 +2623,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e04bb171f6b0016b4d1ec4e1ebb42f07277d2be2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#3a5fe998e6c790223a9ff195df96e1f34ba2c149"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2590,7 +2637,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e04bb171f6b0016b4d1ec4e1ebb42f07277d2be2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#3a5fe998e6c790223a9ff195df96e1f34ba2c149"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"heck",
|
||||
@@ -2603,7 +2650,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
"thiserror",
|
||||
"typify",
|
||||
"unicode-ident",
|
||||
@@ -2612,7 +2659,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e04bb171f6b0016b4d1ec4e1ebb42f07277d2be2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#3a5fe998e6c790223a9ff195df96e1f34ba2c149"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
@@ -2623,7 +2670,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_tokenstream",
|
||||
"serde_yaml",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2723,7 +2770,7 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2759,7 +2806,7 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.9",
|
||||
"redox_syscall 0.2.16",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -2960,16 +3007,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.5"
|
||||
version = "0.37.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75"
|
||||
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2996,7 +3043,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#07bad48dbc47a910e853723c33183874f390e969"
|
||||
source = "git+https://github.com/RustPython/RustPython#dde92d1269b5fbd09cf5dba5e7817f0e2a96c49f"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -3005,7 +3052,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#07bad48dbc47a910e853723c33183874f390e969"
|
||||
source = "git+https://github.com/RustPython/RustPython#dde92d1269b5fbd09cf5dba5e7817f0e2a96c49f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bstr",
|
||||
@@ -3018,7 +3065,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#07bad48dbc47a910e853723c33183874f390e969"
|
||||
source = "git+https://github.com/RustPython/RustPython#dde92d1269b5fbd09cf5dba5e7817f0e2a96c49f"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"anyhow",
|
||||
@@ -3199,9 +3246,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde-aux"
|
||||
version = "4.1.2"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0"
|
||||
checksum = "c3dfe1b7eb6f9dcf011bd6fad169cdeaae75eda0d61b1a99a3f015b41b0cae39"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -3225,7 +3272,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3291,7 +3338,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3308,9 +3355,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_v8"
|
||||
version = "0.88.0"
|
||||
version = "0.90.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b163edac8a2f2536ae7b52f839a33aba7892c04d0cf21e1bf8213ecfc905ebcc"
|
||||
checksum = "916ca7852a4c5f0ba59ce4a46301bf7c7ad573c2c89a0fe67e90fe30dcbd6f7d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"derive_more",
|
||||
@@ -3323,9 +3370,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.19"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10"
|
||||
checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@@ -3689,6 +3736,28 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
@@ -3835,9 +3904,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.12"
|
||||
version = "2.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
|
||||
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3900,7 +3969,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4014,7 +4083,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4309,7 +4378,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.12-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#6d77f63b3dc5312cd36549507f941cb5d783600e"
|
||||
source = "git+https://github.com/oxidecomputer/typify#fec09bd1924a676ac18eecc0dea64b8ec042fdb4"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
@@ -4318,7 +4387,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.12-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#6d77f63b3dc5312cd36549507f941cb5d783600e"
|
||||
source = "git+https://github.com/oxidecomputer/typify#fec09bd1924a676ac18eecc0dea64b8ec042fdb4"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
@@ -4327,7 +4396,7 @@ dependencies = [
|
||||
"regress",
|
||||
"schemars",
|
||||
"serde_json",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
"thiserror",
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -4335,7 +4404,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.12-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#6d77f63b3dc5312cd36549507f941cb5d783600e"
|
||||
source = "git+https://github.com/oxidecomputer/typify#fec09bd1924a676ac18eecc0dea64b8ec042fdb4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4343,7 +4412,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
"typify-impl",
|
||||
]
|
||||
|
||||
@@ -4395,6 +4464,18 @@ dependencies = [
|
||||
"unic-ucd-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-category"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unic-char-property",
|
||||
"unic-char-range",
|
||||
"unic-ucd-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-ident"
|
||||
version = "0.9.0"
|
||||
@@ -4491,9 +4572,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c"
|
||||
checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
@@ -4531,7 +4612,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4540,15 +4621,15 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.9",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v8"
|
||||
version = "0.66.0"
|
||||
version = "0.68.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8ab8597b885c17b3761f6ffc29b7a62758612c409285a9271c6dacd17bb745"
|
||||
checksum = "81c69410b7435f1b74e82e243ba906d71e8b9bb350828291418b9311dbd77222"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fslock",
|
||||
@@ -4780,7 +4861,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4792,6 +4873,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.6",
|
||||
"sqlx",
|
||||
@@ -4807,7 +4889,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4817,6 +4899,7 @@ dependencies = [
|
||||
"axum",
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"cookie",
|
||||
"cron",
|
||||
"futures",
|
||||
@@ -4863,7 +4946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4878,7 +4961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4891,7 +4974,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4916,7 +4999,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4924,7 +5007,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4939,9 +5022,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
"itertools",
|
||||
"phf",
|
||||
"unicode-general-category",
|
||||
@@ -4951,7 +5035,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4966,7 +5050,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4980,10 +5064,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"cron",
|
||||
"hex",
|
||||
"hmac",
|
||||
@@ -5003,7 +5088,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -5039,11 +5124,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.46.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5052,13 +5137,13 @@ version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5067,7 +5152,16 @@ version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5076,13 +5170,28 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5091,42 +5200,84 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
@@ -5190,9 +5341,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.7+zstd.1.5.4"
|
||||
version = "2.0.8+zstd.1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5"
|
||||
checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.84.0"
|
||||
version = "1.86.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -28,11 +28,7 @@ name = "windmill"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[features]
|
||||
enterprise = [
|
||||
"windmill-worker/enterprise",
|
||||
"windmill-queue/enterprise",
|
||||
"windmill-api/enterprise",
|
||||
]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -53,11 +49,13 @@ rsa.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
reqwest.workspace = true
|
||||
windmill-queue.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
[workspace.dependencies]
|
||||
windmill-api = { path = "./windmill-api" }
|
||||
@@ -76,7 +74,7 @@ headers = "^0"
|
||||
hyper = { version = "^0", features = ["full"] }
|
||||
tokio = { version = "^1", features = ["full", "tracing"] }
|
||||
tower = "^0"
|
||||
tower-http = { version = "^0", features = ["trace"] }
|
||||
tower-http = { version = "^0", features = ["trace", "cors"] }
|
||||
tower-cookies = "^0"
|
||||
serde = "^1"
|
||||
serde_json = { version = "^1", features = ["preserve_order"] }
|
||||
@@ -84,6 +82,7 @@ uuid = { version = "^1", features = ["serde", "v4"] }
|
||||
thiserror = "^1"
|
||||
anyhow = "^1"
|
||||
chrono = { version = "^0", features = ["serde"] }
|
||||
chrono-tz = "^0"
|
||||
tracing = "^0"
|
||||
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
|
||||
prometheus = { version = "^0", default-features = false }
|
||||
@@ -153,4 +152,5 @@ async-stripe = { version = "0.14", features = [
|
||||
"checkout",
|
||||
] }
|
||||
async_zip = { version = "0.0.11", features = ["full"] }
|
||||
once_cell = "1.17.1"
|
||||
once_cell = "1.17.1"
|
||||
gosyn = "0.2.2"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE schedule DROP COLUMN timezone;
|
||||
ALTER TABLE schedule ADD COLUMN offset_ INTEGER NOT NULL DEFAULT 0;
|
||||
26
backend/migrations/20230405071524_schedule_timezone.up.sql
Normal file
26
backend/migrations/20230405071524_schedule_timezone.up.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
ALTER TABLE schedule ADD COLUMN timezone VARCHAR(255) NOT NULL DEFAULT 'UTC';
|
||||
|
||||
-- INSERT the correct IANA timezone string for each offset value
|
||||
|
||||
UPDATE schedule SET timezone = 'Pacific/Honolulu' WHERE offset_ = 600;
|
||||
UPDATE schedule SET timezone = 'America/Anchorage' WHERE offset_ = 540;
|
||||
UPDATE schedule SET timezone = 'America/Los_Angeles' WHERE offset_ = 480;
|
||||
UPDATE schedule SET timezone = 'America/Chicago' WHERE offset_ = 360;
|
||||
UPDATE schedule SET timezone = 'America/New_York' WHERE offset_ = 300;
|
||||
UPDATE schedule SET timezone = 'America/Halifax' WHERE offset_ = 240;
|
||||
UPDATE schedule SET timezone = 'America/Sao_Paulo' WHERE offset_ = 180;
|
||||
UPDATE schedule SET timezone = 'Atlantic/South_Georgia' WHERE offset_ = 120;
|
||||
UPDATE schedule SET timezone = 'Atlantic/Cape_Verde' WHERE offset_ = 60;
|
||||
UPDATE schedule SET timezone = 'Europe/London' WHERE offset_ = 0;
|
||||
UPDATE schedule SET timezone = 'Europe/Berlin' WHERE offset_ = -60;
|
||||
UPDATE schedule SET timezone = 'Europe/Athens' WHERE offset_ = -120;
|
||||
UPDATE schedule SET timezone = 'Europe/Moscow' WHERE offset_ = -180;
|
||||
UPDATE schedule SET timezone = 'Asia/Dubai' WHERE offset_ = -240;
|
||||
UPDATE schedule SET timezone = 'Asia/Aqtau' WHERE offset_ = -300;
|
||||
UPDATE schedule SET timezone = 'Asia/Almaty' WHERE offset_ = -360;
|
||||
UPDATE schedule SET timezone = 'Asia/Bangkok' WHERE offset_ = -420;
|
||||
UPDATE schedule SET timezone = 'Asia/Hong_Kong' WHERE offset_ = -480;
|
||||
UPDATE schedule SET timezone = 'Asia/Tokyo' WHERE offset_ = -540;
|
||||
UPDATE schedule SET timezone = 'Australia/Sydney' WHERE offset_ = -600;
|
||||
|
||||
ALTER TABLE schedule DROP COLUMN offset_;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE password ALTER COLUMN company TYPE VARCHAR(255);
|
||||
ALTER TABLE password ALTER COLUMN name TYPE VARCHAR(255);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE pip_resolution_cache;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add up migration script here
|
||||
CREATE TABLE pip_resolution_cache(
|
||||
hash VARCHAR(255) PRIMARY KEY,
|
||||
expiration TIMESTAMP NOT NULL,
|
||||
lockfile TEXT NOT NULL
|
||||
);
|
||||
@@ -15,3 +15,4 @@ phf.workspace = true
|
||||
unicode-general-category.workspace = true
|
||||
itertools.workspace = true
|
||||
anyhow.workspace = true
|
||||
gosyn.workspace = true
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,347 +0,0 @@
|
||||
#![allow(clippy::large_enum_variant)] // TODO: we allow large enum variant for now, let's profile properly to see if we want to box.
|
||||
|
||||
use crate::parser_go_token::{Position, Token};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// https://pkg.go.dev/go/ast#CommentGroup
|
||||
#[derive(Debug)]
|
||||
pub struct CommentGroup {
|
||||
// List []*Comment // len(List) > 0
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#FieldList
|
||||
#[derive(Debug)]
|
||||
pub struct FieldList<'a> {
|
||||
pub opening: Option<Position<'a>>, // position of opening parenthesis/brace, if any
|
||||
pub list: Vec<Field<'a>>, // field list; or nil
|
||||
pub closing: Option<Position<'a>>, // position of closing parenthesis/brace, if any
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Field
|
||||
#[derive(Debug)]
|
||||
pub struct Field<'a> {
|
||||
pub doc: Option<CommentGroup>, // associated documentation; or nil
|
||||
pub names: Option<Vec<Ident<'a>>>, // field/method/(type) parameter names, or type "type"; or nil
|
||||
pub type_: Option<Expr<'a>>, // field/method/parameter type, type list type; or nil
|
||||
pub tag: Option<BasicLit<'a>>, // field tag; or nil
|
||||
pub comment: Option<CommentGroup>, // line comments; or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#File
|
||||
#[derive(Debug)]
|
||||
pub struct File<'a> {
|
||||
// package name
|
||||
pub decls: Vec<Decl<'a>>, // top-level declarations; or nil // list of all comments in the source file
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#FuncDecl
|
||||
#[derive(Debug)]
|
||||
pub struct FuncDecl<'a> {
|
||||
pub doc: Option<CommentGroup>, // associated documentation; or nil
|
||||
pub recv: Option<FieldList<'a>>, // receiver (methods); or nil (functions)
|
||||
pub name: Ident<'a>, // function/method name
|
||||
pub type_: FuncType<'a>, // function signature: type and value parameters, results, and position of "func" keyword
|
||||
pub body: Option<BlockStmt<'a>>, // function body; or nil for external (non-Go) function
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#BlockStmt
|
||||
#[derive(Debug)]
|
||||
pub struct BlockStmt<'a> {
|
||||
pub lbrace: Position<'a>, // position of "{"
|
||||
pub list: Vec<Stmt>,
|
||||
pub rbrace: Position<'a>, // position of "}", if any (may be absent due to syntax error)
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#FuncType
|
||||
#[derive(Debug)]
|
||||
pub struct FuncType<'a> {
|
||||
pub func: Option<Position<'a>>, // position of "func" keyword (token.NoPos if there is no "func")
|
||||
pub params: FieldList<'a>, // (incoming) parameters; non-nil
|
||||
pub results: Option<FieldList<'a>>, // (outgoing) results; or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Ident
|
||||
#[derive(Debug)]
|
||||
pub struct Ident<'a> {
|
||||
pub name_pos: Position<'a>, // identifier position
|
||||
pub name: &'a str, // identifier name
|
||||
pub obj: Option<Box<Object<'a>>>, // denoted object; or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ValueSpec
|
||||
#[derive(Debug)]
|
||||
pub struct ValueSpec<'a> {
|
||||
pub doc: Option<CommentGroup>, // associated documentation; or nil
|
||||
pub names: Vec<Ident<'a>>, // value names (len(Names) > 0)
|
||||
pub type_: Option<Expr<'a>>, // value type; or nil
|
||||
pub values: Option<Vec<Expr<'a>>>, // initial values; or nil
|
||||
pub comment: Option<CommentGroup>, // line comments; or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#BasicLit
|
||||
#[derive(Debug)]
|
||||
pub struct BasicLit<'a> {
|
||||
pub value_pos: Position<'a>, // literal position
|
||||
pub kind: Token, // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING
|
||||
pub value: &'a str, // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o`
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Object
|
||||
#[derive(Debug)]
|
||||
pub struct Object<'a> {
|
||||
pub kind: ObjKind,
|
||||
pub name: &'a str, // declared name
|
||||
pub decl: Option<ObjDecl>, // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil
|
||||
pub data: Option<usize>, // object-specific data; or nil
|
||||
pub type_: Option<()>, // placeholder for type information; may be nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Ellipsis
|
||||
#[derive(Debug)]
|
||||
pub struct Ellipsis<'a> {
|
||||
pub ellipsis: Position<'a>, // position of "..."
|
||||
pub elt: Option<Box<Expr<'a>>>, // ellipsis element type (parameter lists only); or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Ellipsis
|
||||
#[derive(Debug)]
|
||||
pub struct TypeAssertExpr<'a> {
|
||||
pub x: Box<Expr<'a>>, // expression
|
||||
pub lparen: Position<'a>, // position of "("
|
||||
pub type_: Box<Expr<'a>>, // asserted type; nil means type switch X.(type)
|
||||
pub rparen: Position<'a>, // position of ")"
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#SliceExpr
|
||||
#[derive(Debug)]
|
||||
pub struct SliceExpr<'a> {
|
||||
pub x: Box<Expr<'a>>, // expression
|
||||
pub lbrack: Position<'a>, // position of "["
|
||||
pub low: Option<Box<Expr<'a>>>, // begin of slice range; or nil
|
||||
pub high: Option<Box<Expr<'a>>>, // end of slice range; or nil
|
||||
pub max: Option<Box<Expr<'a>>>, // maximum capacity of slice; or nil
|
||||
pub slice3: bool, // true if 3-index slice (2 colons present)
|
||||
pub rbrack: Position<'a>, // position of "]"
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ObjKind
|
||||
#[derive(Debug)]
|
||||
pub enum ObjKind {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ObjDecl {}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Decl
|
||||
#[derive(Debug)]
|
||||
pub enum Decl<'a> {
|
||||
FuncDecl(FuncDecl<'a>),
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Scope
|
||||
#[derive(Debug)]
|
||||
pub struct Scope<'a> {
|
||||
pub outer: Option<Box<Scope<'a>>>,
|
||||
pub objects: BTreeMap<&'a str, Object<'a>>,
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#GenDecl
|
||||
#[derive(Debug)]
|
||||
pub struct GenDecl<'a> {
|
||||
pub doc: Option<CommentGroup>, // associated documentation; or nil
|
||||
pub tok_pos: Position<'a>, // position of Tok
|
||||
pub tok: Token, // IMPORT, CONST, TYPE, or VAR
|
||||
pub lparen: Option<Position<'a>>, // position of '(', if any
|
||||
pub specs: Vec<Spec>,
|
||||
pub rparen: Option<Position<'a>>, // position of ')', if any
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#AssignStmt
|
||||
#[derive(Debug)]
|
||||
pub struct AssignStmt<'a> {
|
||||
pub lhs: Vec<Expr<'a>>,
|
||||
pub tok_pos: Position<'a>, // position of Tok
|
||||
pub tok: Token, // assignment token, DEFINE
|
||||
pub rhs: Vec<Expr<'a>>,
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#BinaryExpr
|
||||
#[derive(Debug)]
|
||||
pub struct BinaryExpr<'a> {
|
||||
pub x: Box<Expr<'a>>, // left operand
|
||||
pub op_pos: Position<'a>, // position of Op
|
||||
pub op: Token, // operator
|
||||
pub y: Box<Expr<'a>>, // right operand
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ReturnStmt
|
||||
#[derive(Debug)]
|
||||
pub struct ReturnStmt<'a> {
|
||||
pub return_: Position<'a>, // position of "return" keyword
|
||||
pub results: Vec<Expr<'a>>, // result expressions; or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#TypeSpec
|
||||
#[derive(Debug)]
|
||||
pub struct TypeSpec<'a> {
|
||||
pub doc: Option<CommentGroup>, // associated documentation; or nil
|
||||
pub name: Option<Ident<'a>>, // type name
|
||||
pub assign: Option<Position<'a>>, // position of '=', if any
|
||||
pub type_: Expr<'a>, // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes
|
||||
pub comment: Option<CommentGroup>, // line comments; or nil
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#StructType
|
||||
#[derive(Debug)]
|
||||
pub struct StructType<'a> {
|
||||
pub struct_: Position<'a>, // position of "struct" keyword
|
||||
pub fields: Option<FieldList<'a>>, // list of field declarations
|
||||
pub incomplete: bool, // true if (source) fields are missing in the Fields list
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#StarExpr
|
||||
#[derive(Debug)]
|
||||
pub struct StarExpr<'a> {
|
||||
pub star: Position<'a>, // position of "*"
|
||||
pub x: Box<Expr<'a>>, // operand
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#InterfaceType
|
||||
#[derive(Debug)]
|
||||
pub struct InterfaceType<'a> {
|
||||
pub interface: Position<'a>, // position of "interface" keyword
|
||||
pub methods: Option<FieldList<'a>>, // list of embedded interfaces, methods, or types
|
||||
pub incomplete: bool, // true if (source) methods or types are missing in the Methods list
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#UnaryExpr
|
||||
#[derive(Debug)]
|
||||
pub struct UnaryExpr<'a> {
|
||||
pub op_pos: Position<'a>, // position of Op
|
||||
pub op: Token, // operator
|
||||
pub x: Box<Expr<'a>>, // operand
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#CallExpr
|
||||
#[derive(Debug)]
|
||||
pub struct CallExpr<'a> {
|
||||
pub fun: Box<Expr<'a>>, // function expression
|
||||
pub lparen: Position<'a>, // position of "("
|
||||
pub args: Option<Vec<Expr<'a>>>, // function arguments; or nil
|
||||
pub ellipsis: Option<Position<'a>>, // position of "..." (token.NoPos if there is no "...")
|
||||
pub rparen: Position<'a>, // position of ")"
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#SelectorExpr
|
||||
#[derive(Debug)]
|
||||
pub struct SelectorExpr<'a> {
|
||||
pub x: Box<Expr<'a>>, // expression
|
||||
pub sel: Ident<'a>, // field selector
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ParenExpr
|
||||
#[derive(Debug)]
|
||||
pub struct ParenExpr<'a> {
|
||||
pub lparen: Position<'a>, // position of "("
|
||||
pub x: Box<Expr<'a>>, // parenthesized expression
|
||||
pub rparen: Position<'a>, // position of ")"
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#FuncLit
|
||||
#[derive(Debug)]
|
||||
pub struct FuncLit<'a> {
|
||||
pub type_: FuncType<'a>, // function type
|
||||
pub body: BlockStmt<'a>, // function body
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ChanType
|
||||
#[derive(Debug)]
|
||||
pub struct ChanType<'a> {
|
||||
pub begin: Position<'a>, // position of "chan" keyword or "<-" (whichever comes first)
|
||||
pub arrow: Option<Position<'a>>, // position of "<-" (token.NoPos if there is no "<-")
|
||||
pub dir: u8, // channel direction
|
||||
pub value: Box<Expr<'a>>, // value type
|
||||
}
|
||||
|
||||
// htt/opt/visual-studio-code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.htmlps://pkg.go.dev/go/ast#IndexExpr
|
||||
#[derive(Debug)]
|
||||
pub struct IndexExpr<'a> {
|
||||
pub x: Box<Expr<'a>>, // expression
|
||||
pub lbrack: Position<'a>, // position of "["
|
||||
pub index: Box<Expr<'a>>, // index expression
|
||||
pub rbrack: Position<'a>, // position of "]"
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#MapType
|
||||
#[derive(Debug)]
|
||||
pub struct MapType<'a> {
|
||||
pub map: Position<'a>,
|
||||
pub key: Box<Expr<'a>>,
|
||||
pub value: Box<Expr<'a>>,
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#CompositeLit
|
||||
#[derive(Debug)]
|
||||
pub struct CompositeLit<'a> {
|
||||
pub type_: Box<Expr<'a>>, // literal type; or nil
|
||||
pub lbrace: Position<'a>, // position of "{"
|
||||
pub elts: Option<Vec<Expr<'a>>>, // list of composite elements; or nil
|
||||
pub rbrace: Position<'a>, // position of "}"
|
||||
pub incomplete: bool, // true if (source) expressions are missing in the Elts list
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#KeyValueExpr
|
||||
#[derive(Debug)]
|
||||
pub struct KeyValueExpr<'a> {
|
||||
pub key: Box<Expr<'a>>,
|
||||
pub colon: Position<'a>, // position of ":"
|
||||
pub value: Box<Expr<'a>>,
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ArrayType
|
||||
#[derive(Debug)]
|
||||
pub struct ArrayType<'a> {
|
||||
pub lbrack: Position<'a>, // position of "["
|
||||
pub len: Option<Box<Expr<'a>>>, // Ellipsis node for [...]T array types, nil for slice types
|
||||
pub elt: Box<Expr<'a>>, // element type
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#ChanDir
|
||||
#[derive(Debug)]
|
||||
pub enum ChanDir {
|
||||
SEND = 1 << 0,
|
||||
RECV = 1 << 1,
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Spec
|
||||
#[derive(Debug)]
|
||||
pub enum Spec {}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Expr
|
||||
#[derive(Debug)]
|
||||
pub enum Expr<'a> {
|
||||
ArrayType(ArrayType<'a>),
|
||||
BasicLit(BasicLit<'a>),
|
||||
BinaryExpr(BinaryExpr<'a>),
|
||||
CallExpr(CallExpr<'a>),
|
||||
ChanType(ChanType<'a>),
|
||||
CompositeLit(CompositeLit<'a>),
|
||||
Ellipsis(Ellipsis<'a>),
|
||||
FuncLit(FuncLit<'a>),
|
||||
FuncType(FuncType<'a>),
|
||||
Ident(Ident<'a>),
|
||||
IndexExpr(IndexExpr<'a>),
|
||||
InterfaceType(InterfaceType<'a>),
|
||||
KeyValueExpr(KeyValueExpr<'a>),
|
||||
MapType(MapType<'a>),
|
||||
ParenExpr(ParenExpr<'a>),
|
||||
SelectorExpr(SelectorExpr<'a>),
|
||||
SliceExpr(SliceExpr<'a>),
|
||||
StarExpr(StarExpr<'a>),
|
||||
StructType(StructType<'a>),
|
||||
TypeAssertExpr(TypeAssertExpr<'a>),
|
||||
UnaryExpr(UnaryExpr<'a>),
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/go/ast#Stmt
|
||||
#[derive(Debug)]
|
||||
pub enum Stmt {}
|
||||
@@ -1,948 +0,0 @@
|
||||
// https://golang.org/ref/spec#Lexical_elements
|
||||
|
||||
use crate::parser_go_token::{Position, Token};
|
||||
use phf::{phf_map, Map};
|
||||
use std::fmt;
|
||||
use unicode_general_category::{get_general_category, GeneralCategory};
|
||||
|
||||
pub type Step<'a> = (Position<'a>, Token, &'a str);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScannerError {
|
||||
HexadecimalNotFound,
|
||||
OctalNotFound,
|
||||
UnterminatedComment,
|
||||
UnterminatedEscapedChar,
|
||||
UnterminatedRune,
|
||||
UnterminatedString,
|
||||
InvalidDirective,
|
||||
}
|
||||
|
||||
impl std::error::Error for ScannerError {}
|
||||
|
||||
impl fmt::Display for ScannerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "scanner error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ScannerError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Scanner<'a> {
|
||||
directory: &'a str,
|
||||
file: &'a str,
|
||||
buffer: &'a str,
|
||||
//
|
||||
chars: std::iter::Peekable<std::str::Chars<'a>>,
|
||||
current_char: Option<char>,
|
||||
current_char_len: usize,
|
||||
//
|
||||
offset: usize,
|
||||
line: usize,
|
||||
column: usize,
|
||||
start_offset: usize,
|
||||
start_line: usize,
|
||||
start_column: usize,
|
||||
//
|
||||
hide_column: bool,
|
||||
insert_semi: bool,
|
||||
pending_line_info: Option<LineInfo<'a>>,
|
||||
}
|
||||
|
||||
type LineInfo<'a> = (Option<&'a str>, usize, Option<usize>, bool);
|
||||
|
||||
impl<'a> Scanner<'a> {
|
||||
pub fn new(filename: &'a str, buffer: &'a str) -> Self {
|
||||
let (directory, file) = filename.rsplit_once('/').unwrap_or(("", filename));
|
||||
let mut s = Scanner {
|
||||
directory,
|
||||
file,
|
||||
buffer,
|
||||
//
|
||||
chars: buffer.chars().peekable(),
|
||||
current_char: None,
|
||||
current_char_len: 0,
|
||||
//
|
||||
offset: 0,
|
||||
line: 1,
|
||||
column: 1,
|
||||
start_offset: 0,
|
||||
start_line: 1,
|
||||
start_column: 1,
|
||||
//
|
||||
hide_column: false,
|
||||
insert_semi: false,
|
||||
pending_line_info: None,
|
||||
};
|
||||
s.next(); // read the first character
|
||||
s
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)] // Allow complex scan function
|
||||
pub fn scan(&mut self) -> Result<Step<'a>> {
|
||||
let insert_semi = self.insert_semi;
|
||||
self.insert_semi = false;
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
self.reset_start();
|
||||
|
||||
match c {
|
||||
' ' | '\t' | '\r' => {
|
||||
self.next();
|
||||
}
|
||||
|
||||
'\n' => {
|
||||
self.next();
|
||||
if insert_semi {
|
||||
return Ok((self.position(), Token::SEMICOLON, "\n"));
|
||||
}
|
||||
}
|
||||
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(c) = self.current_char {
|
||||
match c {
|
||||
'+' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::ADD_ASSIGN, ""));
|
||||
}
|
||||
Some('+') => {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
return Ok((self.position(), Token::INC, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::ADD, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'-' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::SUB_ASSIGN, ""));
|
||||
}
|
||||
Some('-') => {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
return Ok((self.position(), Token::DEC, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::SUB, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'*' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::MUL_ASSIGN, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::MUL, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'/' => match self.peek() {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
self.next();
|
||||
return Ok((self.position(), Token::QUO_ASSIGN, ""));
|
||||
}
|
||||
Some('/') => {
|
||||
if insert_semi {
|
||||
return Ok((self.position(), Token::SEMICOLON, "\n"));
|
||||
}
|
||||
return self.scan_line_comment();
|
||||
}
|
||||
Some('*') => {
|
||||
if insert_semi && self.find_line_end() {
|
||||
return Ok((self.position(), Token::SEMICOLON, "\n"));
|
||||
}
|
||||
return self.scan_general_comment();
|
||||
}
|
||||
_ => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::QUO, ""));
|
||||
}
|
||||
},
|
||||
|
||||
'%' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::REM_ASSIGN, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::REM, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'&' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::AND_ASSIGN, ""));
|
||||
}
|
||||
Some('&') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::LAND, ""));
|
||||
}
|
||||
Some('^') => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::AND_NOT_ASSIGN, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::AND_NOT, "")),
|
||||
}
|
||||
}
|
||||
_ => return Ok((self.position(), Token::AND, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'|' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::OR_ASSIGN, ""));
|
||||
}
|
||||
Some('|') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::LOR, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::OR, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'^' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::XOR_ASSIGN, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::XOR, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'<' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('<') => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::SHL_ASSIGN, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::SHL, "")),
|
||||
}
|
||||
}
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::LEQ, ""));
|
||||
}
|
||||
Some('-') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::ARROW, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::LSS, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'>' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('>') => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::SHR_ASSIGN, ""));
|
||||
}
|
||||
_ => {
|
||||
return Ok((self.position(), Token::SHR, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::GEQ, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::GTR, "")),
|
||||
}
|
||||
}
|
||||
|
||||
':' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::DEFINE, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::COLON, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'!' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::NEQ, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::NOT, "")),
|
||||
}
|
||||
}
|
||||
|
||||
',' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::COMMA, ""));
|
||||
}
|
||||
|
||||
'(' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::LPAREN, ""));
|
||||
}
|
||||
|
||||
')' => {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
return Ok((self.position(), Token::RPAREN, ""));
|
||||
}
|
||||
|
||||
'[' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::LBRACK, ""));
|
||||
}
|
||||
|
||||
']' => {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
return Ok((self.position(), Token::RBRACK, ""));
|
||||
}
|
||||
|
||||
'{' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::LBRACE, ""));
|
||||
}
|
||||
|
||||
'}' => {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
return Ok((self.position(), Token::RBRACE, ""));
|
||||
}
|
||||
|
||||
';' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::SEMICOLON, ";"));
|
||||
}
|
||||
|
||||
'.' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('0'..='9') => return self.scan_int_or_float_or_imag(true),
|
||||
Some('.') => match self.peek() {
|
||||
Some('.') => {
|
||||
self.next();
|
||||
self.next();
|
||||
return Ok((self.position(), Token::ELLIPSIS, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::PERIOD, "")),
|
||||
},
|
||||
_ => return Ok((self.position(), Token::PERIOD, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'=' => {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('=') => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::EQL, ""));
|
||||
}
|
||||
_ => return Ok((self.position(), Token::ASSIGN, "")),
|
||||
}
|
||||
}
|
||||
|
||||
'0'..='9' => return self.scan_int_or_float_or_imag(false),
|
||||
'\'' => return self.scan_rune(),
|
||||
'"' => return self.scan_interpreted_string(),
|
||||
'`' => return self.scan_raw_string(),
|
||||
_ => return self.scan_pkg_or_keyword_or_ident(),
|
||||
};
|
||||
}
|
||||
|
||||
self.reset_start();
|
||||
if insert_semi {
|
||||
Ok((self.position(), Token::SEMICOLON, "\n"))
|
||||
} else {
|
||||
Ok((self.position(), Token::EOF, ""))
|
||||
}
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Keywords
|
||||
// https://golang.org/ref/spec#Identifiers
|
||||
fn scan_pkg_or_keyword_or_ident(&mut self) -> Result<Step<'a>> {
|
||||
self.next();
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
if !(is_letter(c) || is_unicode_digit(c)) {
|
||||
break;
|
||||
}
|
||||
self.next()
|
||||
}
|
||||
|
||||
let pos = self.position();
|
||||
let literal = self.literal();
|
||||
|
||||
if literal.len() > 1 {
|
||||
if let Some(&token) = KEYWORDS.get(literal) {
|
||||
self.insert_semi = matches!(
|
||||
token,
|
||||
Token::BREAK | Token::CONTINUE | Token::FALLTHROUGH | Token::RETURN
|
||||
);
|
||||
return Ok((pos, token, literal));
|
||||
}
|
||||
}
|
||||
|
||||
self.insert_semi = true;
|
||||
Ok((pos, Token::IDENT, literal))
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Integer_literals
|
||||
// https://golang.org/ref/spec#Floating-point_literals
|
||||
// https://golang.org/ref/spec#Imaginary_literals
|
||||
fn scan_int_or_float_or_imag(&mut self, preceding_dot: bool) -> Result<Step<'a>> {
|
||||
self.insert_semi = true;
|
||||
|
||||
let mut token = Token::INT;
|
||||
let mut digits = "_0123456789";
|
||||
let mut exp = "eE";
|
||||
|
||||
if !preceding_dot {
|
||||
if matches!(self.current_char, Some('0')) {
|
||||
self.next();
|
||||
match self.current_char {
|
||||
Some('b' | 'B') => {
|
||||
digits = "_01";
|
||||
exp = "";
|
||||
self.next();
|
||||
}
|
||||
Some('o' | 'O') => {
|
||||
digits = "_01234567";
|
||||
exp = "";
|
||||
self.next();
|
||||
}
|
||||
Some('x' | 'X') => {
|
||||
digits = "_0123456789abcdefABCDEF";
|
||||
exp = "pP";
|
||||
self.next();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
if !digits.contains(c) {
|
||||
break;
|
||||
}
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
|
||||
if preceding_dot || matches!(self.current_char, Some('.')) {
|
||||
token = Token::FLOAT;
|
||||
self.next();
|
||||
while let Some(c) = self.current_char {
|
||||
if !digits.contains(c) {
|
||||
break;
|
||||
}
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
|
||||
if !exp.is_empty() {
|
||||
if let Some(c) = self.current_char {
|
||||
if exp.contains(c) {
|
||||
token = Token::FLOAT;
|
||||
self.next();
|
||||
if matches!(self.current_char, Some('-' | '+')) {
|
||||
self.next();
|
||||
}
|
||||
while let Some(c) = self.current_char {
|
||||
if !matches!(c, '_' | '0'..='9') {
|
||||
break;
|
||||
}
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(self.current_char, Some('i')) {
|
||||
token = Token::IMAG;
|
||||
self.next();
|
||||
}
|
||||
|
||||
Ok((self.position(), token, self.literal()))
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Rune_literals
|
||||
fn scan_rune(&mut self) -> Result<Step<'a>> {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
|
||||
match self.current_char {
|
||||
Some('\\') => self.require_escaped_char::<'\''>()?,
|
||||
Some(_) => self.next(),
|
||||
_ => return Err(ScannerError::UnterminatedRune),
|
||||
}
|
||||
|
||||
if matches!(self.current_char, Some('\'')) {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::CHAR, self.literal()));
|
||||
}
|
||||
|
||||
Err(ScannerError::UnterminatedRune)
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#String_literals
|
||||
fn scan_interpreted_string(&mut self) -> Result<Step<'a>> {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
match c {
|
||||
'"' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::STRING, self.literal()));
|
||||
}
|
||||
'\\' => self.require_escaped_char::<'"'>()?,
|
||||
_ => self.next(),
|
||||
}
|
||||
}
|
||||
|
||||
Err(ScannerError::UnterminatedString)
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#String_literals
|
||||
fn scan_raw_string(&mut self) -> Result<Step<'a>> {
|
||||
self.insert_semi = true;
|
||||
self.next();
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
match c {
|
||||
'`' => {
|
||||
self.next();
|
||||
return Ok((self.position(), Token::STRING, self.literal()));
|
||||
}
|
||||
_ => self.next(),
|
||||
}
|
||||
}
|
||||
|
||||
Err(ScannerError::UnterminatedString)
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Comments
|
||||
fn scan_general_comment(&mut self) -> Result<Step<'a>> {
|
||||
self.next();
|
||||
self.next();
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
match c {
|
||||
'*' => {
|
||||
self.next();
|
||||
if matches!(self.current_char, Some('/')) {
|
||||
self.next();
|
||||
|
||||
let pos = self.position();
|
||||
let lit = self.literal();
|
||||
|
||||
// look for compiler directives
|
||||
self.directive(&lit["/*".len()..lit.len() - "*/".len()], true)?;
|
||||
|
||||
return Ok((pos, Token::COMMENT, lit));
|
||||
}
|
||||
}
|
||||
_ => self.next(),
|
||||
}
|
||||
}
|
||||
|
||||
Err(ScannerError::UnterminatedComment)
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Comments
|
||||
fn scan_line_comment(&mut self) -> Result<Step<'a>> {
|
||||
self.next();
|
||||
self.next();
|
||||
|
||||
while let Some(c) = self.current_char {
|
||||
if is_newline(c) {
|
||||
break;
|
||||
}
|
||||
self.next();
|
||||
}
|
||||
|
||||
let pos = self.position();
|
||||
let lit = self.literal();
|
||||
|
||||
// look for compiler directives (at the beginning of line)
|
||||
if self.start_column == 1 {
|
||||
self.directive(lit["//".len()..].trim_end(), false)?;
|
||||
}
|
||||
|
||||
Ok((pos, Token::COMMENT, self.literal()))
|
||||
}
|
||||
|
||||
// https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
|
||||
fn directive(&mut self, input: &'a str, immediate: bool) -> Result<()> {
|
||||
if let Some(line_directive) = input.strip_prefix("line ") {
|
||||
self.pending_line_info = self.parse_line_directive(line_directive)?;
|
||||
if immediate {
|
||||
self.consume_pending_line_info();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_line_directive(&mut self, line_directive: &'a str) -> Result<Option<LineInfo<'a>>> {
|
||||
if let Some((file, line)) = line_directive.rsplit_once(':') {
|
||||
let line = line.parse().map_err(|_| ScannerError::InvalidDirective)?;
|
||||
|
||||
if let Some((file, l)) = file.rsplit_once(':') {
|
||||
if let Ok(l) = l.parse() {
|
||||
//line :line:col
|
||||
//line filename:line:col
|
||||
/*line :line:col*/
|
||||
/*line filename:line:col*/
|
||||
let file = if !file.is_empty() { Some(file) } else { None };
|
||||
let col = Some(line);
|
||||
let line = l;
|
||||
let hide_column = false;
|
||||
return Ok(Some((file, line, col, hide_column)));
|
||||
}
|
||||
}
|
||||
|
||||
//line :line
|
||||
//line filename:line
|
||||
/*line :line*/
|
||||
/*line filename:line*/
|
||||
Ok(Some((Some(file), line, None, true)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
const fn find_line_end(&self) -> bool {
|
||||
let buffer = self.buffer.as_bytes();
|
||||
let mut in_comment = true;
|
||||
|
||||
let mut i = self.offset;
|
||||
let max = self.buffer.len();
|
||||
while i < max {
|
||||
let c = buffer[i] as char;
|
||||
|
||||
if i < max - 1 {
|
||||
let n = buffer[i + 1] as char;
|
||||
|
||||
if !in_comment && c == '/' && n == '/' {
|
||||
return true;
|
||||
}
|
||||
|
||||
if c == '/' && n == '*' {
|
||||
i += 2;
|
||||
in_comment = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '*' && n == '/' {
|
||||
i += 2;
|
||||
in_comment = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if is_newline(c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !in_comment && !matches!(c, ' ' | '\t' | '\r') {
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
!in_comment
|
||||
}
|
||||
|
||||
fn consume_pending_line_info(&mut self) {
|
||||
if let Some(line_info) = self.pending_line_info.take() {
|
||||
if let Some(file) = line_info.0 {
|
||||
self.file = file;
|
||||
}
|
||||
|
||||
self.line = line_info.1;
|
||||
|
||||
if let Some(column) = line_info.2 {
|
||||
self.column = column;
|
||||
}
|
||||
|
||||
self.hide_column = line_info.3;
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Option<char> {
|
||||
self.chars.peek().copied()
|
||||
}
|
||||
|
||||
fn next(&mut self) {
|
||||
self.offset += self.current_char_len;
|
||||
self.column += self.current_char_len;
|
||||
let last_char = self.current_char;
|
||||
|
||||
self.current_char = self.chars.next();
|
||||
if let Some(c) = self.current_char {
|
||||
self.current_char_len = c.len_utf8();
|
||||
if matches!(last_char, Some('\n')) {
|
||||
self.line += 1;
|
||||
self.column = 1;
|
||||
self.consume_pending_line_info();
|
||||
}
|
||||
} else {
|
||||
self.current_char_len = 0
|
||||
}
|
||||
}
|
||||
|
||||
const fn position(&self) -> Position<'a> {
|
||||
Position {
|
||||
directory: self.directory,
|
||||
file: self.file,
|
||||
offset: self.start_offset,
|
||||
line: self.start_line,
|
||||
column: if self.hide_column {
|
||||
0
|
||||
} else {
|
||||
self.start_column
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_start(&mut self) {
|
||||
self.start_offset = self.offset;
|
||||
self.start_line = self.line;
|
||||
self.start_column = self.column;
|
||||
}
|
||||
|
||||
fn literal(&self) -> &'a str {
|
||||
&self.buffer[self.start_offset..self.offset]
|
||||
}
|
||||
|
||||
fn require_escaped_char<const DELIM: char>(&mut self) -> Result<()> {
|
||||
self.next();
|
||||
|
||||
let c = self
|
||||
.current_char
|
||||
.ok_or(ScannerError::UnterminatedEscapedChar)?;
|
||||
|
||||
// TODO: move this to the match when const generics can be referenced in patterns
|
||||
if c == DELIM {
|
||||
self.next();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match c {
|
||||
'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' | '\\' => self.next(),
|
||||
'x' => {
|
||||
self.next();
|
||||
self.require_hex_digits::<2>()?
|
||||
}
|
||||
'u' => {
|
||||
self.next();
|
||||
self.require_hex_digits::<4>()?;
|
||||
}
|
||||
'U' => {
|
||||
self.next();
|
||||
self.require_hex_digits::<8>()?;
|
||||
}
|
||||
'0'..='7' => self.require_octal_digits::<3>()?,
|
||||
_ => return Err(ScannerError::UnterminatedEscapedChar),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_octal_digits<const COUNT: usize>(&mut self) -> Result<()> {
|
||||
for _ in 0..COUNT {
|
||||
let c = self.current_char.ok_or(ScannerError::OctalNotFound)?;
|
||||
|
||||
if !is_octal_digit(c) {
|
||||
return Err(ScannerError::OctalNotFound);
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require_hex_digits<const COUNT: usize>(&mut self) -> Result<()> {
|
||||
for _ in 0..COUNT {
|
||||
let c = self.current_char.ok_or(ScannerError::HexadecimalNotFound)?;
|
||||
|
||||
if !is_hex_digit(c) {
|
||||
return Err(ScannerError::HexadecimalNotFound);
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Scanner<'a> {
|
||||
type Item = Result<Step<'a>>;
|
||||
type IntoIter = IntoIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Self::IntoIter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IntoIter<'a> {
|
||||
scanner: Scanner<'a>,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl<'a> IntoIter<'a> {
|
||||
const fn new(scanner: Scanner<'a>) -> Self {
|
||||
Self { scanner, done: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IntoIter<'a> {
|
||||
type Item = Result<Step<'a>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.scanner.scan() {
|
||||
Ok((pos, tok, lit)) => {
|
||||
if tok == Token::EOF {
|
||||
self.done = true;
|
||||
}
|
||||
Some(Ok((pos, tok, lit)))
|
||||
}
|
||||
Err(err) => {
|
||||
self.done = true;
|
||||
Some(Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Letters_and_digits
|
||||
|
||||
fn is_letter(c: char) -> bool {
|
||||
c == '_' || is_unicode_letter(c)
|
||||
}
|
||||
|
||||
//const fn is_decimal_digit(c: char) -> bool {
|
||||
//matches!(c, '0'..='9')
|
||||
//}
|
||||
|
||||
//const fn is_binary_digit(c: char) -> bool {
|
||||
//matches!(c, '0'..='1')
|
||||
//}
|
||||
|
||||
const fn is_octal_digit(c: char) -> bool {
|
||||
matches!(c, '0'..='7')
|
||||
}
|
||||
|
||||
const fn is_hex_digit(c: char) -> bool {
|
||||
matches!(c, '0'..='9' | 'A'..='F' | 'a'..='f')
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Characters
|
||||
|
||||
const fn is_newline(c: char) -> bool {
|
||||
c == '\n'
|
||||
}
|
||||
|
||||
//const fn is_unicode_char(c: char) -> bool {
|
||||
//c != '\n'
|
||||
//}
|
||||
|
||||
fn is_unicode_letter(c: char) -> bool {
|
||||
matches!(
|
||||
get_general_category(c),
|
||||
GeneralCategory::UppercaseLetter
|
||||
| GeneralCategory::LowercaseLetter
|
||||
| GeneralCategory::TitlecaseLetter
|
||||
| GeneralCategory::ModifierLetter
|
||||
| GeneralCategory::OtherLetter
|
||||
)
|
||||
}
|
||||
|
||||
fn is_unicode_digit(c: char) -> bool {
|
||||
get_general_category(c) == GeneralCategory::DecimalNumber
|
||||
}
|
||||
|
||||
// https://golang.org/ref/spec#Keywords
|
||||
|
||||
static KEYWORDS: Map<&'static str, Token> = phf_map! {
|
||||
"break" => Token::BREAK,
|
||||
"case" => Token::CASE,
|
||||
"chan" => Token::CHAN,
|
||||
"const" => Token::CONST,
|
||||
"continue" => Token::CONTINUE,
|
||||
|
||||
"default" => Token::DEFAULT,
|
||||
"defer" => Token::DEFER,
|
||||
"else" => Token::ELSE,
|
||||
"fallthrough" => Token::FALLTHROUGH,
|
||||
"for" => Token::FOR,
|
||||
|
||||
"func" => Token::FUNC,
|
||||
"go" => Token::GO,
|
||||
"goto" => Token::GOTO,
|
||||
"if" => Token::IF,
|
||||
"import" => Token::IMPORT,
|
||||
|
||||
"interface" => Token::INTERFACE,
|
||||
"map" => Token::MAP,
|
||||
"package" => Token::PACKAGE,
|
||||
"range" => Token::RANGE,
|
||||
"return" => Token::RETURN,
|
||||
|
||||
"select" => Token::SELECT,
|
||||
"struct" => Token::STRUCT,
|
||||
"switch" => Token::SWITCH,
|
||||
"type" => Token::TYPE,
|
||||
"var" => Token::VAR,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Scanner;
|
||||
|
||||
#[test] // fuzz
|
||||
fn it_should_return_an_error_on_missing_line_number() {
|
||||
let input = "/*line :*/";
|
||||
let mut out: Vec<_> = Scanner::new(file!(), input).into_iter().collect();
|
||||
assert!(out.pop().unwrap().is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/go/token/token.go
|
||||
|
||||
#![allow(non_camel_case_types)] // For consistency with the Go tokens
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Position<'a> {
|
||||
pub directory: &'a str,
|
||||
pub file: &'a str,
|
||||
pub offset: usize,
|
||||
pub line: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Position<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.file.is_empty() {
|
||||
write!(f, ":{}:{}", self.line, self.column)
|
||||
} else if self.file.starts_with('/') {
|
||||
write!(f, "{}:{}:{}", self.file, self.line, self.column)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}/{}:{}:{}",
|
||||
self.directory, self.file, self.line, self.column
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Token {
|
||||
EOF,
|
||||
COMMENT,
|
||||
|
||||
IDENT, // main
|
||||
INT, // 12345
|
||||
FLOAT, // 123.45
|
||||
IMAG, // 123.45i
|
||||
CHAR, // 'a'
|
||||
STRING, // "abc"
|
||||
|
||||
ADD, // +
|
||||
SUB, // -
|
||||
MUL, // *
|
||||
QUO, // /
|
||||
REM, // %
|
||||
|
||||
AND, // &
|
||||
OR, // |
|
||||
XOR, // ^
|
||||
SHL, // <<
|
||||
SHR, // >>
|
||||
AND_NOT, // &^
|
||||
|
||||
ADD_ASSIGN, // +=
|
||||
SUB_ASSIGN, // -=
|
||||
MUL_ASSIGN, // *=
|
||||
QUO_ASSIGN, // /=
|
||||
REM_ASSIGN, // %=
|
||||
|
||||
AND_ASSIGN, // &=
|
||||
OR_ASSIGN, // |=
|
||||
XOR_ASSIGN, // ^=
|
||||
SHL_ASSIGN, // <<=
|
||||
SHR_ASSIGN, // >>=
|
||||
AND_NOT_ASSIGN, // &^=
|
||||
|
||||
LAND, // &&
|
||||
LOR, // ||
|
||||
ARROW, // <-
|
||||
INC, // ++
|
||||
DEC, // --
|
||||
|
||||
EQL, // ==
|
||||
LSS, // <
|
||||
GTR, // >
|
||||
ASSIGN, // =
|
||||
NOT, // !
|
||||
|
||||
NEQ, // !=
|
||||
LEQ, // <=
|
||||
GEQ, // >=
|
||||
DEFINE, // :=
|
||||
ELLIPSIS, // ...
|
||||
|
||||
LPAREN, // (
|
||||
LBRACK, // [
|
||||
LBRACE, // {
|
||||
COMMA, // ,
|
||||
PERIOD, // .
|
||||
|
||||
RPAREN, // )
|
||||
RBRACK, // ]
|
||||
RBRACE, // }
|
||||
SEMICOLON, // ;
|
||||
COLON, // :
|
||||
|
||||
BREAK,
|
||||
CASE,
|
||||
CHAN,
|
||||
CONST,
|
||||
CONTINUE,
|
||||
|
||||
DEFAULT,
|
||||
DEFER,
|
||||
ELSE,
|
||||
FALLTHROUGH,
|
||||
FOR,
|
||||
|
||||
FUNC,
|
||||
GO,
|
||||
GOTO,
|
||||
IF,
|
||||
IMPORT,
|
||||
|
||||
INTERFACE,
|
||||
MAP,
|
||||
PACKAGE,
|
||||
RANGE,
|
||||
RETURN,
|
||||
|
||||
SELECT,
|
||||
STRUCT,
|
||||
SWITCH,
|
||||
TYPE,
|
||||
VAR,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub const fn is_assign_op(&self) -> bool {
|
||||
use Token::*;
|
||||
matches!(
|
||||
self,
|
||||
ADD_ASSIGN
|
||||
| SUB_ASSIGN
|
||||
| MUL_ASSIGN
|
||||
| QUO_ASSIGN
|
||||
| REM_ASSIGN
|
||||
| AND_ASSIGN
|
||||
| OR_ASSIGN
|
||||
| XOR_ASSIGN
|
||||
| SHL_ASSIGN
|
||||
| SHR_ASSIGN
|
||||
| AND_NOT_ASSIGN
|
||||
)
|
||||
}
|
||||
|
||||
// https://go.dev/ref/spec#Operator_precedence
|
||||
pub fn precedence(&self) -> u8 {
|
||||
use Token::*;
|
||||
match self {
|
||||
MUL | QUO | REM | SHL | SHR | AND | AND_NOT => 5,
|
||||
ADD | SUB | OR | XOR => 4,
|
||||
EQL | NEQ | LSS | LEQ | GTR | GEQ => 3,
|
||||
LAND => 2,
|
||||
LOR => 1,
|
||||
_ => unreachable!(
|
||||
"precedence() is only supported for binary operators, called with: {:?}",
|
||||
self
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn lowest_precedence() -> u8 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Token> for &'static str {
|
||||
fn from(token: &Token) -> Self {
|
||||
use Token::*;
|
||||
|
||||
match token {
|
||||
EOF => "EOF",
|
||||
COMMENT => "COMMENT",
|
||||
|
||||
IDENT => "IDENT",
|
||||
INT => "INT",
|
||||
FLOAT => "FLOAT",
|
||||
IMAG => "IMAG",
|
||||
CHAR => "CHAR",
|
||||
STRING => "STRING",
|
||||
|
||||
ADD => "+",
|
||||
SUB => "-",
|
||||
MUL => "*",
|
||||
QUO => "/",
|
||||
REM => "%",
|
||||
|
||||
AND => "&",
|
||||
OR => "|",
|
||||
XOR => "^",
|
||||
SHL => "<<",
|
||||
SHR => ">>",
|
||||
AND_NOT => "&^",
|
||||
|
||||
ADD_ASSIGN => "+=",
|
||||
SUB_ASSIGN => "-=",
|
||||
MUL_ASSIGN => "*=",
|
||||
QUO_ASSIGN => "/=",
|
||||
REM_ASSIGN => "%=",
|
||||
|
||||
AND_ASSIGN => "&=",
|
||||
OR_ASSIGN => "|=",
|
||||
XOR_ASSIGN => "^=",
|
||||
SHL_ASSIGN => "<<=",
|
||||
SHR_ASSIGN => ">>=",
|
||||
AND_NOT_ASSIGN => "&^=",
|
||||
|
||||
LAND => "&&",
|
||||
LOR => "||",
|
||||
ARROW => "<-",
|
||||
INC => "++",
|
||||
DEC => "--",
|
||||
|
||||
EQL => "==",
|
||||
LSS => "<",
|
||||
GTR => ">",
|
||||
ASSIGN => "=",
|
||||
NOT => "!",
|
||||
|
||||
NEQ => "!=",
|
||||
LEQ => "<=",
|
||||
GEQ => ">=",
|
||||
DEFINE => ":=",
|
||||
ELLIPSIS => "...",
|
||||
|
||||
LPAREN => "(",
|
||||
LBRACK => "[",
|
||||
LBRACE => "{",
|
||||
COMMA => ",",
|
||||
PERIOD => ".",
|
||||
|
||||
RPAREN => ")",
|
||||
RBRACK => "]",
|
||||
RBRACE => "}",
|
||||
SEMICOLON => ";",
|
||||
COLON => ":",
|
||||
|
||||
BREAK => "break",
|
||||
CASE => "case",
|
||||
CHAN => "chan",
|
||||
CONST => "const",
|
||||
CONTINUE => "continue",
|
||||
|
||||
DEFAULT => "default",
|
||||
DEFER => "defer",
|
||||
ELSE => "else",
|
||||
FALLTHROUGH => "fallthrough",
|
||||
FOR => "for",
|
||||
|
||||
FUNC => "func",
|
||||
GO => "go",
|
||||
GOTO => "goto",
|
||||
IF => "if",
|
||||
IMPORT => "import",
|
||||
|
||||
INTERFACE => "interface",
|
||||
MAP => "map",
|
||||
PACKAGE => "package",
|
||||
RANGE => "range",
|
||||
RETURN => "return",
|
||||
|
||||
SELECT => "select",
|
||||
STRUCT => "struct",
|
||||
SWITCH => "switch",
|
||||
TYPE => "type",
|
||||
VAR => "var",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ pub fn parse_python_imports(code: &str) -> error::Result<Vec<String>> {
|
||||
let ast = parser::parse_program(code, "main.py").map_err(|e| {
|
||||
error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string()))
|
||||
})?;
|
||||
let imports = ast
|
||||
let mut imports: Vec<String> = ast
|
||||
.into_iter()
|
||||
.filter_map(|x| match x {
|
||||
Located { node, .. } => match node {
|
||||
@@ -250,7 +250,7 @@ pub fn parse_python_imports(code: &str) -> error::Result<Vec<String>> {
|
||||
.filter(|x| !STDIMPORTS.contains(&x.as_str()))
|
||||
.unique()
|
||||
.collect();
|
||||
|
||||
imports.sort();
|
||||
Ok(imports)
|
||||
}
|
||||
}
|
||||
@@ -460,7 +460,7 @@ def main():
|
||||
";
|
||||
let r = parse_python_imports(code)?;
|
||||
// println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(r, vec!["wmill", "zanzibar", "matplotlib", "requests"]);
|
||||
assert_eq!(r, vec!["matplotlib", "requests", "wmill", "zanzibar"]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -6,21 +6,22 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
use deno_core::{serde_v8, v8, JsRuntime, RuntimeOptions};
|
||||
use serde_json::Value;
|
||||
use windmill_common::error;
|
||||
use windmill_parser::{json_to_typ, Arg, MainArgSignature, ObjectProperty, Typ};
|
||||
|
||||
use swc_common::{sync::Lrc, FileName, SourceMap, SourceMapper, Spanned};
|
||||
use swc_common::{sync::Lrc, FileName, SourceMap, SourceMapper, Span, Spanned};
|
||||
use swc_ecma_ast::{
|
||||
ArrayLit, AssignPat, BigInt, BindingIdent, Bool, Decl, ExportDecl, Expr, FnDecl, Ident, Lit,
|
||||
ModuleDecl, ModuleItem, Number, ObjectLit, Pat, Str, TsArrayType, TsEntityName, TsKeywordType,
|
||||
TsKeywordTypeKind, TsLit, TsLitType, TsOptionalType, TsPropertySignature, TsType,
|
||||
TsTypeElement, TsTypeLit, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
|
||||
ModuleDecl, ModuleItem, Number, ObjectLit, Param, Pat, Str, TsArrayType, TsEntityName,
|
||||
TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, TsOptionalType, TsPropertySignature,
|
||||
TsType, TsTypeElement, TsTypeLit, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
|
||||
};
|
||||
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
|
||||
|
||||
pub fn parse_deno_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
pub fn parse_deno_signature(code: &str, skip_dflt: bool) -> error::Result<MainArgSignature> {
|
||||
let cm: Lrc<SourceMap> = Default::default();
|
||||
let fm = cm.new_source_file(FileName::Custom("test.ts".into()), code.into());
|
||||
let fm = cm.new_source_file(FileName::Custom("main.ts".into()), code.into());
|
||||
let lexer = Lexer::new(
|
||||
// We want to parse ecmascript
|
||||
Syntax::Typescript(TsConfig::default()),
|
||||
@@ -54,65 +55,17 @@ pub fn parse_deno_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
})) if &sym.to_string() == "main" => Some(function.params),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Some(params) = params {
|
||||
Ok(MainArgSignature {
|
||||
let r = MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: params
|
||||
.into_iter()
|
||||
.map(|x| match x.pat {
|
||||
Pat::Ident(ident) => {
|
||||
let (name, typ, nullable) = binding_ident_to_arg(&ident);
|
||||
Ok(Arg {
|
||||
otyp: None,
|
||||
name,
|
||||
typ,
|
||||
default: None,
|
||||
has_default: ident.id.optional || nullable,
|
||||
})
|
||||
}
|
||||
Pat::Assign(AssignPat { left, right, .. }) => {
|
||||
let (name, mut typ, _nullable) =
|
||||
left.as_ident().map(binding_ident_to_arg).ok_or_else(|| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"parameter syntax unsupported: `{}`",
|
||||
cm.span_to_snippet(left.span())
|
||||
.unwrap_or_else(|_| cm.span_to_string(left.span()))
|
||||
))
|
||||
})?;
|
||||
|
||||
let span = match *right {
|
||||
Expr::Lit(Lit::Str(Str { span, .. })) => Some(span),
|
||||
Expr::Lit(Lit::Num(Number { span, .. })) => Some(span),
|
||||
Expr::Lit(Lit::BigInt(BigInt { span, .. })) => Some(span),
|
||||
Expr::Lit(Lit::Bool(Bool { span, .. })) => Some(span),
|
||||
Expr::Object(ObjectLit { span, .. }) => Some(span),
|
||||
Expr::Array(ArrayLit { span, .. }) => Some(span),
|
||||
_ => None,
|
||||
};
|
||||
let expr = span
|
||||
.and_then(|x| cm.span_to_snippet(x).ok())
|
||||
.map(|x| serde_json::from_str(&x).map_err(|_| x));
|
||||
|
||||
let default = match expr.clone() {
|
||||
Some(Ok(x)) => Some(x),
|
||||
Some(Err(x)) => eval_sync(&x).ok(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
if typ == Typ::Unknown && default.is_some() {
|
||||
typ = json_to_typ(default.as_ref().unwrap());
|
||||
}
|
||||
Ok(Arg { otyp: None, name, typ, default, has_default: true })
|
||||
}
|
||||
_ => Err(error::Error::ExecutionErr(format!(
|
||||
"parameter syntax unsupported: `{}`",
|
||||
cm.span_to_snippet(x.span())
|
||||
.unwrap_or_else(|_| cm.span_to_string(x.span()))
|
||||
))),
|
||||
})
|
||||
.map(|x| parse_param(x, &cm, skip_dflt))
|
||||
.collect::<Result<Vec<Arg>, error::Error>>()?,
|
||||
})
|
||||
};
|
||||
Ok(r)
|
||||
} else {
|
||||
Err(error::Error::ExecutionErr(
|
||||
"main function was not findable (expected to find 'export function main(...)'"
|
||||
@@ -121,6 +74,70 @@ pub fn parse_deno_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_param(x: Param, cm: &Lrc<SourceMap>, skip_dflt: bool) -> error::Result<Arg> {
|
||||
let r = match x.pat {
|
||||
Pat::Ident(ident) => {
|
||||
let (name, typ, nullable) = binding_ident_to_arg(&ident);
|
||||
Ok(Arg {
|
||||
otyp: None,
|
||||
name,
|
||||
typ,
|
||||
default: None,
|
||||
has_default: ident.id.optional || nullable,
|
||||
})
|
||||
}
|
||||
Pat::Assign(AssignPat { left, right, .. }) => {
|
||||
let (name, mut typ, _nullable) =
|
||||
left.as_ident().map(binding_ident_to_arg).ok_or_else(|| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"parameter syntax unsupported: `{}`",
|
||||
cm.span_to_snippet(left.span())
|
||||
.unwrap_or_else(|_| cm.span_to_string(left.span()))
|
||||
))
|
||||
})?;
|
||||
|
||||
let dflt = if skip_dflt {
|
||||
None
|
||||
} else {
|
||||
match *right {
|
||||
Expr::Lit(Lit::Str(Str { value, .. })) => {
|
||||
Some(Value::String(value.to_string()))
|
||||
}
|
||||
Expr::Lit(Lit::Num(Number { value, .. })) => Some(serde_json::json!(value)),
|
||||
Expr::Lit(Lit::BigInt(BigInt { value, .. })) => Some(serde_json::json!(value)),
|
||||
Expr::Lit(Lit::Bool(Bool { value, .. })) => Some(Value::Bool(value)),
|
||||
Expr::Object(ObjectLit { span, .. }) => eval_span(span, cm),
|
||||
Expr::Array(ArrayLit { span, .. }) => eval_span(span, cm),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
if typ == Typ::Unknown && dflt.is_some() {
|
||||
typ = json_to_typ(dflt.as_ref().unwrap());
|
||||
}
|
||||
Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true })
|
||||
}
|
||||
_ => Err(error::Error::ExecutionErr(format!(
|
||||
"parameter syntax unsupported: `{}`",
|
||||
cm.span_to_snippet(x.span())
|
||||
.unwrap_or_else(|_| cm.span_to_string(x.span()))
|
||||
))),
|
||||
};
|
||||
r
|
||||
}
|
||||
|
||||
fn eval_span(span: Span, cm: &Lrc<SourceMap>) -> Option<Value> {
|
||||
let expr = cm
|
||||
.span_to_snippet(span)
|
||||
.ok()
|
||||
.map(|x| serde_json::from_str(&x).map_err(|_| x));
|
||||
|
||||
match expr {
|
||||
Some(Ok(x)) => Some(x),
|
||||
Some(Err(x)) => eval_sync(&x).ok(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
fn binding_ident_to_arg(BindingIdent { id, type_ann }: &BindingIdent) -> (String, Typ, bool) {
|
||||
let (typ, nullable) = type_ann
|
||||
.as_ref()
|
||||
@@ -282,7 +299,7 @@ export function main(test1?: string, test2: string = \"burkina\",
|
||||
}
|
||||
";
|
||||
assert_eq!(
|
||||
parse_deno_signature(code)?,
|
||||
parse_deno_signature(code, false)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
@@ -394,7 +411,7 @@ export function main(test2 = \"burkina\",
|
||||
}
|
||||
";
|
||||
assert_eq!(
|
||||
parse_deno_signature(code)?,
|
||||
parse_deno_signature(code, false)?,
|
||||
MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
|
||||
@@ -1319,6 +1319,24 @@
|
||||
},
|
||||
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE name = $1 AND workspace_id = $2"
|
||||
},
|
||||
"399a8337a2488fa2ce3da2ef3281a34f8f96ee0d833c2fe3c22a0aa43e306f09": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "hash",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "DELETE FROM pip_resolution_cache WHERE expiration <= now() RETURNING hash"
|
||||
},
|
||||
"39bae7dff750565183ac3893b51a8846d7db2f130a6795b54ef94acc232dec8b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1790,6 +1808,19 @@
|
||||
},
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)\n WHERE id = $2\n "
|
||||
},
|
||||
"4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2"
|
||||
},
|
||||
"5061c0d054bf4f028e7fe51a8f9389024c6ae4492755cadac0f7167e5300bda0": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -1993,101 +2024,6 @@
|
||||
},
|
||||
"query": "INSERT INTO token\n (token, email, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, $5)"
|
||||
},
|
||||
"55960eb3cd5a82f593496349a2521607f8066d88ff11a2ca4a63b6e37e62b71b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE schedule SET schedule = $1, args = $2 WHERE path = $3 AND workspace_id = $4 RETURNING *"
|
||||
},
|
||||
"56839d3aec6c0177d14589aedda8d5c431d841b6d5d0d99ce3836bb42d4d83d9": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -2602,6 +2538,102 @@
|
||||
},
|
||||
"query": "UPDATE flow SET path = $1, summary = $2, description = $3, value = $4, edited_by = $5, edited_at = now(), schema = $6::text::json, dependency_job = NULL WHERE path = $7 AND workspace_id = $8"
|
||||
},
|
||||
"7226d470c04e58fcab92f3edac21956d2a10e5ed6b594747b22682f05a60a341": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE schedule SET schedule = $1, timezone = $2, args = $3 WHERE path = $4 AND workspace_id = $5 RETURNING *"
|
||||
},
|
||||
"73d3ed17fd0723ba75722f394904f6ee306b59aaf8ecfcf56484d38541343f06": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -3007,6 +3039,26 @@
|
||||
},
|
||||
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3"
|
||||
},
|
||||
"866b26e56fd376368c759db6eee13f92373e308f584903121b4546ad51aef86e": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "lockfile",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1"
|
||||
},
|
||||
"8876fa929ffb175cd976a2bca1195704aa9fe7215013ae29e49ef15cb201ba57": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -3195,45 +3247,45 @@
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -3244,12 +3296,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -3472,45 +3524,45 @@
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -3521,12 +3573,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -3659,45 +3711,45 @@
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -3708,12 +3760,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -4237,45 +4289,45 @@
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -4286,12 +4338,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -4812,45 +4864,45 @@
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -4861,12 +4913,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
@@ -5690,107 +5742,6 @@
|
||||
},
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)\n WHERE id = $2\n RETURNING (flow_status->'modules'->$1::int->'iterator'->>'index')::int\n "
|
||||
},
|
||||
"e5ba82116000d2ae71e2099f417b39121178349fda9f8438bdd5556ff632ceb2": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO schedule (workspace_id, path, schedule, offset_, edited_by, script_path, is_flow, args, enabled, email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *"
|
||||
},
|
||||
"e6f85cdbe681ace495fde31e67339dc58460b6914440473fe94de7a7bc292af4": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -5885,6 +5836,107 @@
|
||||
},
|
||||
"query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, slack_name = $3, slack_email = $4"
|
||||
},
|
||||
"ebbabe9b71804ee983235db7d7844052139c23ae7a27cae925c19e1c01037143": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "workspace_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_by",
|
||||
"ordinal": 2,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "edited_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "schedule",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO schedule (workspace_id, path, schedule, timezone, edited_by, script_path, is_flow, args, enabled, email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *"
|
||||
},
|
||||
"ebc06efe51532f7f60e98f62d09a3453fc1d97d93e96ec849eac96dca05236f0": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -5963,45 +6015,45 @@
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "offset_",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "enabled",
|
||||
"ordinal": 6,
|
||||
"ordinal": 5,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "script_path",
|
||||
"ordinal": 7,
|
||||
"ordinal": 6,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "args",
|
||||
"ordinal": 8,
|
||||
"ordinal": 7,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "extra_perms",
|
||||
"ordinal": 9,
|
||||
"ordinal": 8,
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"name": "is_flow",
|
||||
"ordinal": 10,
|
||||
"ordinal": 9,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 11,
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "error",
|
||||
"ordinal": 12,
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"ordinal": 12,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -6012,12 +6064,12 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#[cfg(feature = "enterprise")]
|
||||
use base64::Engine;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use rsa::{pkcs8::DecodePublicKey, signature::Verifier};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use sha2::Sha256;
|
||||
@@ -11,9 +13,9 @@ pub fn verify_license_key(license_key: Option<String>) -> anyhow::Result<()> {
|
||||
.expect("license_key can be splitted with a .");
|
||||
|
||||
let pub_key = rsa::RsaPublicKey::from_public_key_der(
|
||||
&base64::decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgVShzcLSPiOi+8ET8fggob1kmi47/cE12JaidPkwfGnScZItghkqtiLsct0U4kJhlp5gO89DYTBmIKadvxwY7kMsLlZzmi2emVH7c27cByGASY8QmWDNdG4Ggy/NDflGGBdAtN6gHawZAg4zHv3qpbPQGHH1/6sXIohcXhOnouwIDAQAB")?)?;
|
||||
let msg = base64::decode(splitted_lk.0)?;
|
||||
let signature = base64::decode(splitted_lk.1)?;
|
||||
&base64::engine::general_purpose::STANDARD.decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgVShzcLSPiOi+8ET8fggob1kmi47/cE12JaidPkwfGnScZItghkqtiLsct0U4kJhlp5gO89DYTBmIKadvxwY7kMsLlZzmi2emVH7c27cByGASY8QmWDNdG4Ggy/NDflGGBdAtN6gHawZAg4zHv3qpbPQGHH1/6sXIohcXhOnouwIDAQAB")?)?;
|
||||
let msg = base64::engine::general_purpose::STANDARD.decode(splitted_lk.0)?;
|
||||
let signature = base64::engine::general_purpose::STANDARD.decode(splitted_lk.1)?;
|
||||
rsa::pss::VerifyingKey::<Sha256>::new(pub_key)
|
||||
.verify(&msg, &rsa::pss::Signature::from(signature))
|
||||
.map_err(|_| anyhow::anyhow!("Invalid license key".to_string()))?;
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
use git_version::git_version;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::utils::rd_string;
|
||||
use windmill_common::{utils::rd_string, METRICS_ADDR};
|
||||
|
||||
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
const DEFAULT_NUM_WORKERS: usize = 3;
|
||||
@@ -30,15 +30,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
.unwrap_or(DEFAULT_NUM_WORKERS as i32);
|
||||
|
||||
let metrics_addr: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
.ok()
|
||||
.map(|s| {
|
||||
s.parse::<bool>()
|
||||
.map(|b| b.then(|| SocketAddr::from(([0, 0, 0, 0], 8001))))
|
||||
.or_else(|_| s.parse::<SocketAddr>().map(Some))
|
||||
})
|
||||
.transpose()?
|
||||
.flatten();
|
||||
let metrics_addr: Option<SocketAddr> = *METRICS_ADDR;
|
||||
|
||||
let server_bind_address: IpAddr = std::env::var("SERVER_BIND_ADDR")
|
||||
.ok()
|
||||
@@ -156,9 +148,11 @@ Windmill Community Edition {GIT_VERSION}
|
||||
|
||||
let metrics_f = async {
|
||||
match metrics_addr {
|
||||
Some(addr) => windmill_common::serve_metrics(addr, rx.resubscribe())
|
||||
.await
|
||||
.map_err(anyhow::Error::from),
|
||||
Some(addr) => {
|
||||
windmill_common::serve_metrics(addr, rx.resubscribe(), num_workers > 0)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use futures::{stream, Stream};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use sqlx::{postgres::PgListener, types::Uuid, Pool, Postgres, Transaction};
|
||||
use windmill_api::jobs::{CompletedJob, Job};
|
||||
@@ -252,9 +253,7 @@ mod suspend_resume {
|
||||
let second = completed.next().await.unwrap();
|
||||
// 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();
|
||||
tx.commit().await.unwrap();
|
||||
let token = windmill_worker::create_token_for_owner(&db, "test-workspace", "u/test-user", "", 100, "").await.unwrap();
|
||||
let secret = reqwest::get(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/job_signature/{second}/0?token={token}&approver=ruben"
|
||||
))
|
||||
@@ -355,9 +354,7 @@ mod suspend_resume {
|
||||
/* ... and send a request resume it. */
|
||||
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();
|
||||
tx.commit().await.unwrap();
|
||||
let token = windmill_worker::create_token_for_owner(&db, "test-workspace", "u/test-user", "", 100, "").await.unwrap();
|
||||
let secret = reqwest::get(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/job_signature/{second}/0?token={token}"
|
||||
))
|
||||
@@ -1518,6 +1515,8 @@ async fn test_go_job(db: Pool<Postgres>) {
|
||||
let port = server.addr.port();
|
||||
|
||||
let content = r#"
|
||||
package inner
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main(derp string) (string, error) {
|
||||
@@ -2024,6 +2023,16 @@ async fn test_branchall_simple(db: Pool<Postgres>) {
|
||||
assert_eq!(result, serde_json::json!([[1, 2], [1, 3]]));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ErrorResult {
|
||||
error: NamedError,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NamedError {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_branchall_skip_failure(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
@@ -2059,8 +2068,11 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!([{"error": {"name": "Error", "stack": "Error: failure\n at main (file:///tmp/main.ts:1:31)\n at run (file:///tmp/wrapper.ts:9:26)\n at file:///tmp/wrapper.ts:14:1", "message": "failure"}}, [1,3]])
|
||||
serde_json::from_value::<ErrorResult>(result)
|
||||
.unwrap()
|
||||
.error
|
||||
.name,
|
||||
"Error"
|
||||
);
|
||||
|
||||
let flow: FlowValue = serde_json::from_value(json!({
|
||||
@@ -2093,8 +2105,11 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!([ {"error": {"name": "Error", "stack": "Error: failure\n at main (file:///tmp/main.ts:1:31)\n at run (file:///tmp/wrapper.ts:9:26)\n at file:///tmp/wrapper.ts:14:1", "message": "failure"}}, [1, 2]])
|
||||
serde_json::from_value::<ErrorResult>(result)
|
||||
.unwrap()
|
||||
.error
|
||||
.name,
|
||||
"Error"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ tracing.workspace = true
|
||||
sql-builder.workspace = true
|
||||
serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
hex.workspace = true
|
||||
base64.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.84.0
|
||||
version: 1.86.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -1048,6 +1048,27 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/tokens/impersonate:
|
||||
post:
|
||||
summary: create token to impersonate a user (require superadmin)
|
||||
operationId: createTokenImpersonate
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
description: new token
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewTokenImpersonate"
|
||||
responses:
|
||||
"201":
|
||||
description: token created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/tokens/delete/{token_prefix}:
|
||||
delete:
|
||||
summary: delete token
|
||||
@@ -2021,8 +2042,7 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
- name: first_parent_hash
|
||||
description:
|
||||
mask to filter scripts whom first direct parent has exact hash
|
||||
description: mask to filter scripts whom first direct parent has exact hash
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -2279,8 +2299,7 @@ paths:
|
||||
|
||||
/w/{workspace}/scripts/delete/h/{hash}:
|
||||
post:
|
||||
summary:
|
||||
delete script by hash (erase content but keep hash, require admin)
|
||||
summary: delete script by hash (erase content but keep hash, require admin)
|
||||
operationId: deleteScriptByHash
|
||||
tags:
|
||||
- script
|
||||
@@ -2346,6 +2365,24 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/scripts_u/tokened_raw/{workspace}/{token}/{path}:
|
||||
get:
|
||||
summary: raw script by path with a token (mostly used by lsp to be used with import maps to resolve scripts)
|
||||
operationId: rawScriptByPathTokened
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Token"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: script content
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/scripts/exists/p/{path}:
|
||||
get:
|
||||
summary: exists script by path
|
||||
@@ -2435,15 +2472,13 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -2473,18 +2508,6 @@ paths:
|
||||
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/ParentJob"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
@@ -2504,6 +2527,26 @@ paths:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
get:
|
||||
summary: run script by path with get
|
||||
operationId: runWaitResultScriptByPathGet
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: job result
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_wait_result/f/{path}:
|
||||
post:
|
||||
summary: run flow by path and wait until completion
|
||||
@@ -2513,18 +2556,6 @@ paths:
|
||||
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"
|
||||
|
||||
@@ -3023,16 +3054,14 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the flow owner (default false)
|
||||
description: make the run invisible to the the flow owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3070,16 +3099,14 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3110,8 +3137,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3142,8 +3168,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3483,8 +3508,7 @@ paths:
|
||||
|
||||
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
|
||||
get:
|
||||
summary:
|
||||
get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
operationId: getResumeUrls
|
||||
tags:
|
||||
- job
|
||||
@@ -3528,6 +3552,7 @@ paths:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/JobId"
|
||||
- $ref: "#/components/parameters/Payload"
|
||||
- name: resume_id
|
||||
in: path
|
||||
required: true
|
||||
@@ -3741,14 +3766,14 @@ paths:
|
||||
properties:
|
||||
schedule:
|
||||
type: string
|
||||
offset:
|
||||
type: integer
|
||||
timezone:
|
||||
type: string
|
||||
required:
|
||||
- schedule
|
||||
- timezone
|
||||
responses:
|
||||
"200":
|
||||
description:
|
||||
the preview of the next 10 time this schedule would apply to
|
||||
description: List of 5 estimated upcoming execution events (in UTC)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -3935,8 +3960,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: only_member_of
|
||||
in: query
|
||||
description:
|
||||
only list the groups the user is member of (default false)
|
||||
description: only list the groups the user is member of (default false)
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
@@ -4124,8 +4148,7 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: only_member_of
|
||||
in: query
|
||||
description:
|
||||
only list the folders the user is member of (default false)
|
||||
description: only list the folders the user is member of (default false)
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
@@ -4551,6 +4574,12 @@ components:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
Token:
|
||||
name: token
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
AccountId:
|
||||
name: id
|
||||
in: path
|
||||
@@ -4608,8 +4637,7 @@ components:
|
||||
type: integer
|
||||
PerPage:
|
||||
name: per_page
|
||||
description:
|
||||
number of items to return for a given page (default 30, max 100)
|
||||
description: number of items to return for a given page (default 30, max 100)
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -4649,6 +4677,14 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
Payload:
|
||||
name: payload
|
||||
description: |
|
||||
The base64 encoded payload that has been encoded as a JSON. e.g how to encode such payload encodeURIComponent
|
||||
`encodeURIComponent(btoa(JSON.stringify({a: 2})))`
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
|
||||
ScriptStartPath:
|
||||
name: script_path_start
|
||||
@@ -4702,16 +4738,14 @@ components:
|
||||
type: boolean
|
||||
ArgsFilter:
|
||||
name: args
|
||||
description:
|
||||
filter on jobs containing those args as a json subset (@> in postgres)
|
||||
description: filter on jobs containing those args as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
|
||||
ResultFilter:
|
||||
name: result
|
||||
description:
|
||||
filter on jobs containing those result as a json subset (@> in postgres)
|
||||
description: filter on jobs containing those result as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -5143,6 +5177,19 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
NewTokenImpersonate:
|
||||
type: object
|
||||
properties:
|
||||
label:
|
||||
type: string
|
||||
expiration:
|
||||
type: string
|
||||
format: date-time
|
||||
impersonate_email:
|
||||
type: string
|
||||
required:
|
||||
- impersonate_email
|
||||
|
||||
ListableVariable:
|
||||
type: object
|
||||
properties:
|
||||
@@ -5268,6 +5315,11 @@ components:
|
||||
MainArgSignature:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: ["Valid", "Invalid"]
|
||||
error:
|
||||
type: string
|
||||
star_args:
|
||||
type: boolean
|
||||
star_kwargs:
|
||||
@@ -5377,6 +5429,8 @@ components:
|
||||
- star_args
|
||||
- start_kwargs
|
||||
- args
|
||||
- type
|
||||
- error
|
||||
|
||||
Preview:
|
||||
type: object
|
||||
@@ -5511,8 +5565,8 @@ components:
|
||||
format: date-time
|
||||
schedule:
|
||||
type: string
|
||||
offset_:
|
||||
type: integer
|
||||
timezone:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
script_path:
|
||||
@@ -5535,7 +5589,7 @@ components:
|
||||
- edited_at
|
||||
- schedule
|
||||
- script_path
|
||||
- offset_
|
||||
- timezone
|
||||
- extra_perms
|
||||
- is_flow
|
||||
- enabled
|
||||
@@ -5548,8 +5602,8 @@ components:
|
||||
type: string
|
||||
schedule:
|
||||
type: string
|
||||
offset:
|
||||
type: integer
|
||||
timezone:
|
||||
type: string
|
||||
script_path:
|
||||
type: string
|
||||
is_flow:
|
||||
@@ -5561,6 +5615,7 @@ components:
|
||||
required:
|
||||
- path
|
||||
- schedule
|
||||
- timezone
|
||||
- script_path
|
||||
- is_flow
|
||||
- args
|
||||
@@ -5570,10 +5625,13 @@ components:
|
||||
properties:
|
||||
schedule:
|
||||
type: string
|
||||
timezone:
|
||||
type: string
|
||||
args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
required:
|
||||
- schedule
|
||||
- timezone
|
||||
- script_path
|
||||
- is_flow
|
||||
- args
|
||||
|
||||
@@ -30,7 +30,7 @@ use windmill_queue::{push, schedule::push_scheduled_job, JobPayload};
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
users::{maybe_refresh_folders, require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
@@ -178,12 +178,15 @@ async fn check_path_conflict<'c>(
|
||||
|
||||
async fn create_flow(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(nf): Json<NewFlow>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
// cron::Schedule::from_str(&ns.schedule).map_err(|e| error::Error::BadRequest(e.to_string()))?;
|
||||
let authed = maybe_refresh_folders(&nf.path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
check_path_conflict(&mut tx, &w_id, &nf.path).await?;
|
||||
@@ -288,9 +291,11 @@ async fn update_flow(
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Json(nf): Json<NewFlow>,
|
||||
) -> Result<String> {
|
||||
let flow_path = flow_path.to_path();
|
||||
let authed = maybe_refresh_folders(&flow_path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
let flow_path = flow_path.to_path();
|
||||
check_schedule_conflict(&mut tx, &w_id, flow_path).await?;
|
||||
|
||||
let schema = nf.schema.map(|x| x.0);
|
||||
@@ -625,7 +630,6 @@ mod tests {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"input_transforms": {},
|
||||
"value": {
|
||||
"input_transforms": {
|
||||
"test": {
|
||||
@@ -639,7 +643,6 @@ mod tests {
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"input_transforms": {},
|
||||
"value": {
|
||||
"input_transforms": {},
|
||||
"type": "rawscript",
|
||||
@@ -653,7 +656,6 @@ mod tests {
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"input_transforms": {},
|
||||
"value": {
|
||||
"type": "forloopflow",
|
||||
"iterator": {
|
||||
@@ -676,7 +678,6 @@ mod tests {
|
||||
],
|
||||
"failure_module": {
|
||||
"id": "d",
|
||||
"input_transforms": {},
|
||||
"value": {
|
||||
"input_transforms": {},
|
||||
"type": "script",
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::Authed,
|
||||
users::{AuthCache, Authed, Tokened},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
@@ -144,8 +146,10 @@ lazy_static! {
|
||||
|
||||
async fn create_folder(
|
||||
authed: Authed,
|
||||
Tokened { token }: Tokened,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(cache): Extension<Arc<AuthCache>>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ng): Json<NewFolder>,
|
||||
) -> Result<String> {
|
||||
@@ -156,8 +160,8 @@ async fn create_folder(
|
||||
"Folder name can only contain alphanumeric characters, underscores"
|
||||
)));
|
||||
}
|
||||
|
||||
check_name_conflict(&mut tx, &w_id, &ng.name).await?;
|
||||
cache.invalidate(&w_id, token).await;
|
||||
let owner = username_to_permissioned_as(&authed.username);
|
||||
let owners = &ng.owners.unwrap_or(vec![owner.clone()]);
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ pub fn workspaced_service() -> Router {
|
||||
"/run_wait_result/p/*script_path",
|
||||
post(run_wait_result_job_by_path),
|
||||
)
|
||||
.route(
|
||||
"/run_wait_result/p/*script_path",
|
||||
get(run_wait_result_job_by_path_get),
|
||||
)
|
||||
.route(
|
||||
"/run_wait_result/h/:hash",
|
||||
post(run_wait_result_job_by_hash),
|
||||
@@ -309,6 +313,7 @@ pub struct RunJobQuery {
|
||||
include_header: Option<String>,
|
||||
invisible_to_owner: Option<bool>,
|
||||
queue_limit: Option<i64>,
|
||||
payload: Option<String>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -1194,7 +1199,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
|
||||
fn decode_payload<D: DeserializeOwned>(t: String) -> anyhow::Result<D> {
|
||||
let vec = base64::engine::general_purpose::URL_SAFE
|
||||
.decode(t)
|
||||
.context("invalid base64")?;
|
||||
@@ -1385,6 +1390,60 @@ lazy_static::lazy_static! {
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(20);
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_job_by_path_get(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
) -> error::JsonResult<serde_json::Value> {
|
||||
let payload_r = run_query
|
||||
.payload
|
||||
.map(decode_payload)
|
||||
.map(|x| x.map_err(|e| Error::InternalErr(e.to_string())));
|
||||
|
||||
let args = if let Some(payload) = payload_r {
|
||||
payload?
|
||||
} else {
|
||||
serde_json::Map::new()
|
||||
};
|
||||
|
||||
check_queue_too_long(db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?;
|
||||
let script_path = script_path.to_path();
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
|
||||
|
||||
let (uuid, tx) = push(
|
||||
tx,
|
||||
&w_id,
|
||||
job_payload,
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
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_WAIT_RESULT,
|
||||
uuid,
|
||||
Path((w_id, script_path)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_job_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -1398,7 +1457,6 @@ pub async fn run_wait_result_job_by_path(
|
||||
let script_path = script_path.to_path();
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
|
||||
let scheduled_for = run_query.get_scheduled_for(&mut tx).await?;
|
||||
|
||||
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
|
||||
|
||||
@@ -1410,7 +1468,7 @@ pub async fn run_wait_result_job_by_path(
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
@@ -1446,7 +1504,6 @@ pub async fn run_wait_result_job_by_hash(
|
||||
let hash = script_hash.0;
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let path = get_path_for_hash(&mut tx, &w_id, hash).await?;
|
||||
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(
|
||||
@@ -1457,7 +1514,7 @@ pub async fn run_wait_result_job_by_hash(
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
|
||||
@@ -11,11 +11,15 @@ use argon2::Argon2;
|
||||
use axum::{middleware::from_extractor, routing::get, Extension, Router};
|
||||
use db::DB;
|
||||
use git_version::git_version;
|
||||
use hyper::Method;
|
||||
use reqwest::Client;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_cookies::CookieManagerLayer;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tower_http::{
|
||||
cors::{Any, CorsLayer},
|
||||
trace::TraceLayer,
|
||||
};
|
||||
use windmill_common::utils::rd_string;
|
||||
|
||||
use crate::{
|
||||
@@ -100,6 +104,11 @@ pub async fn run_server(
|
||||
.layer(Extension(auth_cache.clone()))
|
||||
.layer(CookieManagerLayer::new())
|
||||
.layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone())));
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods([Method::GET, Method::POST])
|
||||
.allow_origin(Any);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.nest(
|
||||
@@ -109,7 +118,7 @@ pub async fn run_server(
|
||||
"/w/:workspace_id",
|
||||
Router::new()
|
||||
.nest("/scripts", scripts::workspaced_service())
|
||||
.nest("/jobs", jobs::workspaced_service())
|
||||
.nest("/jobs", jobs::workspaced_service().layer(cors.clone()))
|
||||
.nest(
|
||||
"/users",
|
||||
users::workspaced_service().layer(Extension(argon2.clone())),
|
||||
@@ -136,16 +145,25 @@ pub async fn run_server(
|
||||
.nest("/workers", worker_ping::global_service())
|
||||
.nest("/scripts", scripts::global_service())
|
||||
.nest("/flows", flows::global_service())
|
||||
.nest("/apps", apps::global_service())
|
||||
.nest("/apps", apps::global_service().layer(cors.clone()))
|
||||
.nest("/schedules", schedule::global_service())
|
||||
.route_layer(from_extractor::<Authed>())
|
||||
.route_layer(from_extractor::<users::Tokened>())
|
||||
.nest("/scripts_u", scripts::global_unauthed_service())
|
||||
.nest(
|
||||
"/w/:workspace_id/apps_u",
|
||||
apps::unauthed_service().layer(from_extractor::<OptAuthed>()),
|
||||
apps::unauthed_service()
|
||||
.layer(from_extractor::<OptAuthed>())
|
||||
.layer(cors.clone()),
|
||||
)
|
||||
.nest(
|
||||
"/w/:workspace_id/jobs_u",
|
||||
jobs::global_service().layer(cors.clone()),
|
||||
)
|
||||
.nest(
|
||||
"/w/:workspace_id/capture_u",
|
||||
capture::global_service().layer(cors),
|
||||
)
|
||||
.nest("/w/:workspace_id/jobs_u", jobs::global_service())
|
||||
.nest("/w/:workspace_id/capture_u", capture::global_service())
|
||||
.nest(
|
||||
"/auth",
|
||||
users::make_unauthed_service().layer(Extension(argon2)),
|
||||
|
||||
@@ -849,7 +849,8 @@ async fn login_callback(
|
||||
_ => user.email.ok_or_else(|| {
|
||||
error::Error::BadRequest("email address not fetchable from user info".to_string())
|
||||
})?,
|
||||
};
|
||||
}
|
||||
.to_lowercase();
|
||||
|
||||
if let Some(domains) = &client_w_config.allowed_domains {
|
||||
if !domains.iter().any(|d| email.ends_with(d)) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::{require_owner_of_path, Authed},
|
||||
users::{maybe_refresh_folders, require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
@@ -264,9 +264,12 @@ async fn create_resource(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(resource): Json<CreateResource>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let authed = maybe_refresh_folders(&resource.path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
check_path_conflict(&mut tx, &w_id, &resource.path).await?;
|
||||
@@ -375,6 +378,7 @@ async fn update_resource(
|
||||
}
|
||||
|
||||
sqlb.returning("path");
|
||||
let authed = maybe_refresh_folders(path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
|
||||
@@ -6,20 +6,19 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::Authed,
|
||||
users::{maybe_refresh_folders, Authed},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use std::str::FromStr;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
error::{Error, JsonResult, Result},
|
||||
@@ -47,7 +46,7 @@ pub fn global_service() -> Router {
|
||||
pub struct NewSchedule {
|
||||
pub path: String,
|
||||
pub schedule: String,
|
||||
pub offset: i32,
|
||||
pub timezone: String,
|
||||
pub script_path: String,
|
||||
pub is_flow: bool,
|
||||
pub args: Option<serde_json::Value>,
|
||||
@@ -78,10 +77,13 @@ async fn check_path_conflict<'c>(
|
||||
|
||||
async fn create_schedule(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ns): Json<NewSchedule>,
|
||||
) -> Result<String> {
|
||||
let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
cron::Schedule::from_str(&ns.schedule).map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
check_path_conflict(&mut tx, &w_id, &ns.path).await?;
|
||||
@@ -89,12 +91,12 @@ async fn create_schedule(
|
||||
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
"INSERT INTO schedule (workspace_id, path, schedule, offset_, edited_by, script_path, \
|
||||
"INSERT INTO schedule (workspace_id, path, schedule, timezone, edited_by, script_path, \
|
||||
is_flow, args, enabled, email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *",
|
||||
w_id,
|
||||
ns.path,
|
||||
ns.schedule,
|
||||
ns.offset,
|
||||
ns.timezone,
|
||||
&authed.username,
|
||||
ns.script_path,
|
||||
ns.is_flow,
|
||||
@@ -135,13 +137,17 @@ async fn create_schedule(
|
||||
|
||||
async fn edit_schedule(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(es): Json<EditSchedule>,
|
||||
) -> Result<String> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let path = path.to_path();
|
||||
|
||||
let authed = maybe_refresh_folders(&path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
cron::Schedule::from_str(&es.schedule).map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
|
||||
let is_flow = sqlx::query_scalar!(
|
||||
@@ -155,9 +161,10 @@ async fn edit_schedule(
|
||||
clear_schedule(&mut tx, path, is_flow).await?;
|
||||
let schedule = sqlx::query_as!(
|
||||
Schedule,
|
||||
"UPDATE schedule SET schedule = $1, args = $2 WHERE path \
|
||||
= $3 AND workspace_id = $4 RETURNING *",
|
||||
"UPDATE schedule SET schedule = $1, timezone = $2, args = $3 WHERE path \
|
||||
= $4 AND workspace_id = $5 RETURNING *",
|
||||
es.schedule,
|
||||
es.timezone,
|
||||
es.args,
|
||||
path,
|
||||
w_id,
|
||||
@@ -235,15 +242,26 @@ async fn exists_schedule(
|
||||
Ok(Json(res))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PreviewPayload {
|
||||
pub schedule: String,
|
||||
pub timezone: String,
|
||||
}
|
||||
|
||||
pub async fn preview_schedule(
|
||||
Json(payload): Json<PreviewPayload>,
|
||||
) -> JsonResult<Vec<DateTime<chrono::Utc>>> {
|
||||
) -> JsonResult<Vec<DateTime<Utc>>> {
|
||||
let schedule = cron::Schedule::from_str(&payload.schedule)
|
||||
.map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
let upcoming: Vec<DateTime<chrono::Utc>> = schedule
|
||||
.upcoming(get_offset(payload.offset))
|
||||
.take(10)
|
||||
.map(|x| x.into())
|
||||
|
||||
let tz =
|
||||
chrono_tz::Tz::from_str(&payload.timezone).map_err(|e| Error::BadRequest(e.to_string()))?;
|
||||
|
||||
let upcoming: Vec<DateTime<Utc>> = schedule
|
||||
.upcoming(tz)
|
||||
.take(5)
|
||||
// Convert back to UTC for a standardised API response. The client will convert to the local timezone.
|
||||
.map(|x| x.with_timezone(&Utc))
|
||||
.collect();
|
||||
|
||||
Ok(Json(upcoming))
|
||||
@@ -353,6 +371,7 @@ async fn check_flow_conflict<'c>(
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditSchedule {
|
||||
pub schedule: String,
|
||||
pub timezone: String,
|
||||
pub args: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
@@ -376,16 +395,6 @@ pub async fn clear_schedule<'c>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PreviewPayload {
|
||||
pub schedule: String,
|
||||
pub offset: Option<i32>,
|
||||
}
|
||||
|
||||
fn get_offset(offset: Option<i32>) -> FixedOffset {
|
||||
FixedOffset::west_opt(offset.unwrap_or(0) * 60).expect("Invalid offset")
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetEnabled {
|
||||
pub enabled: bool,
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
|
||||
use sql_builder::prelude::*;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_parser::MainArgSignature;
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
users::{maybe_refresh_folders, require_owner_of_path, AuthCache, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
@@ -29,6 +30,7 @@ use sqlx::{FromRow, Postgres, Transaction};
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
use windmill_common::{
|
||||
error::{Error, JsonResult, Result},
|
||||
@@ -60,6 +62,13 @@ pub fn global_service() -> Router {
|
||||
.route("/hub/get_full/*path", get(get_full_hub_script_by_path))
|
||||
}
|
||||
|
||||
pub fn global_unauthed_service() -> Router {
|
||||
Router::new().route(
|
||||
"/tokened_raw/:workspace/:token/*path",
|
||||
get(get_tokened_raw_script_by_path),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
.route("/list", get(list_scripts))
|
||||
@@ -190,6 +199,7 @@ async fn create_script(
|
||||
Json(ns): Json<NewScript>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let hash = ScriptHash(hash_script(&ns));
|
||||
let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
if sqlx::query_scalar!(
|
||||
@@ -496,6 +506,18 @@ async fn list_paths(
|
||||
Ok(Json(scripts))
|
||||
}
|
||||
|
||||
async fn get_tokened_raw_script_by_path(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, token, path)): Path<(String, String, StripPath)>,
|
||||
Extension(cache): Extension<Arc<AuthCache>>,
|
||||
) -> Result<String> {
|
||||
let authed = cache
|
||||
.get_authed(Some(w_id.clone()), &token)
|
||||
.await
|
||||
.ok_or_else(|| Error::NotAuthorized("Invalid token".to_string()))?;
|
||||
return raw_script_by_path(authed, Extension(user_db), Path((w_id, path))).await;
|
||||
}
|
||||
|
||||
async fn raw_script_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -556,11 +578,10 @@ async fn get_script_by_hash_internal<'c>(
|
||||
}
|
||||
|
||||
async fn get_script_by_hash(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, hash)): Path<(String, ScriptHash)>,
|
||||
) -> JsonResult<Script> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let mut tx = db.begin().await?;
|
||||
let r = get_script_by_hash_internal(&mut tx, &w_id, &hash).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -568,11 +589,10 @@ async fn get_script_by_hash(
|
||||
}
|
||||
|
||||
async fn raw_script_by_hash(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, hash_str)): Path<(String, String)>,
|
||||
) -> Result<String> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let mut tx = db.begin().await?;
|
||||
let hash = ScriptHash(to_i64(hash_str.strip_suffix(".ts").ok_or_else(|| {
|
||||
Error::BadRequest("Raw script path must end with .ts".to_string())
|
||||
})?)?);
|
||||
@@ -588,11 +608,10 @@ struct DeploymentStatus {
|
||||
lock_error_logs: Option<String>,
|
||||
}
|
||||
async fn get_deployment_status(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, hash)): Path<(String, ScriptHash)>,
|
||||
) -> JsonResult<DeploymentStatus> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let mut tx = db.begin().await?;
|
||||
let status_o: Option<DeploymentStatus> = sqlx::query_as!(
|
||||
DeploymentStatus,
|
||||
"SELECT lock, lock_error_logs FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
@@ -761,25 +780,31 @@ async fn delete_script_by_path(
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
async fn parse_python_code_to_jsonschema(
|
||||
Json(code): Json<String>,
|
||||
) -> JsonResult<windmill_parser::MainArgSignature> {
|
||||
windmill_parser_py::parse_python_signature(&code).map(Json)
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum SigParsing {
|
||||
Valid(MainArgSignature),
|
||||
Invalid { error: String },
|
||||
}
|
||||
|
||||
async fn parse_deno_code_to_jsonschema(
|
||||
Json(code): Json<String>,
|
||||
) -> JsonResult<windmill_parser::MainArgSignature> {
|
||||
windmill_parser_ts::parse_deno_signature(&code).map(Json)
|
||||
}
|
||||
async fn parse_go_code_to_jsonschema(
|
||||
Json(code): Json<String>,
|
||||
) -> JsonResult<windmill_parser::MainArgSignature> {
|
||||
windmill_parser_go::parse_go_sig(&code).map(Json)
|
||||
fn result_to_sig_parsing(result: Result<MainArgSignature>) -> Json<SigParsing> {
|
||||
match result {
|
||||
Ok(sig) => Json(SigParsing::Valid(sig)),
|
||||
Err(e) => Json(SigParsing::Invalid { error: e.to_string() }),
|
||||
}
|
||||
}
|
||||
|
||||
async fn parse_bash_code_to_jsonschema(
|
||||
Json(code): Json<String>,
|
||||
) -> JsonResult<windmill_parser::MainArgSignature> {
|
||||
windmill_parser_bash::parse_bash_sig(&code).map(Json)
|
||||
async fn parse_python_code_to_jsonschema(Json(code): Json<String>) -> Json<SigParsing> {
|
||||
result_to_sig_parsing(windmill_parser_py::parse_python_signature(&code))
|
||||
}
|
||||
|
||||
async fn parse_deno_code_to_jsonschema(Json(code): Json<String>) -> Json<SigParsing> {
|
||||
result_to_sig_parsing(windmill_parser_ts::parse_deno_signature(&code, false))
|
||||
}
|
||||
async fn parse_go_code_to_jsonschema(Json(code): Json<String>) -> Json<SigParsing> {
|
||||
result_to_sig_parsing(windmill_parser_go::parse_go_sig(&code))
|
||||
}
|
||||
|
||||
async fn parse_bash_code_to_jsonschema(Json(code): Json<String>) -> Json<SigParsing> {
|
||||
result_to_sig_parsing(windmill_parser_bash::parse_bash_sig(&code))
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ pub fn global_service() -> Router {
|
||||
.route("/tokens/create", post(create_token))
|
||||
.route("/tokens/delete/:token_prefix", delete(delete_token))
|
||||
.route("/tokens/list", get(list_tokens))
|
||||
.route("/tokens/impersonate", post(impersonate))
|
||||
.route("/usage", get(get_usage))
|
||||
// .route("/list_invite_codes", get(list_invite_codes))
|
||||
// .route("/create_invite_code", post(create_invite_code))
|
||||
@@ -102,6 +103,10 @@ impl AuthCache {
|
||||
AuthCache { cache: Cache::new(), db, superadmin_secret }
|
||||
}
|
||||
|
||||
pub async fn invalidate(&self, w_id: &str, token: String) {
|
||||
self.cache.remove(&(w_id.to_string(), token)).await;
|
||||
}
|
||||
|
||||
pub async fn get_authed(&self, w_id: Option<String>, token: &str) -> Option<Authed> {
|
||||
let key = (
|
||||
w_id.as_ref().unwrap_or(&"".to_string()).to_string(),
|
||||
@@ -356,6 +361,31 @@ pub struct Authed {
|
||||
pub folders: Vec<(String, bool)>,
|
||||
}
|
||||
|
||||
pub async fn maybe_refresh_folders(path: &str, w_id: &str, authed: Authed, db: &DB) -> Authed {
|
||||
if authed.is_admin {
|
||||
return authed;
|
||||
}
|
||||
let splitted = path.split('/').collect::<Vec<_>>();
|
||||
if splitted.len() >= 2
|
||||
&& splitted[0] == "f"
|
||||
&& !authed.folders.iter().any(|(f, _)| f == splitted[1])
|
||||
{
|
||||
let name = &authed.username;
|
||||
let groups = get_groups_for_user(w_id, name, db)
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
let folders = get_folders_for_user(w_id, name, &groups, db)
|
||||
.await
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
Authed { folders, ..authed }
|
||||
} else {
|
||||
authed
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for Authed
|
||||
where
|
||||
@@ -564,6 +594,7 @@ pub struct TruncatedToken {
|
||||
pub struct NewToken {
|
||||
pub label: Option<String>,
|
||||
pub expiration: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub impersonate_email: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -1009,10 +1040,6 @@ async fn accept_invite(
|
||||
Extension(db): Extension<DB>,
|
||||
Json(nu): Json<AcceptInvite>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
if &nu.username == "bot" {
|
||||
return Err(Error::BadRequest("bot is a reserved username".to_string()));
|
||||
}
|
||||
|
||||
if !VALID_USERNAME.is_match(&nu.username) {
|
||||
return Err(windmill_common::error::Error::BadRequest(format!(
|
||||
"Usermame can only contain alphanumeric characters and underscores"
|
||||
@@ -1296,11 +1323,12 @@ async fn create_user(
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(argon2): Extension<Arc<Argon2<'_>>>,
|
||||
Json(nu): Json<NewUser>,
|
||||
Json(mut nu): Json<NewUser>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
require_super_admin(&mut tx, &email).await?;
|
||||
nu.email = nu.email.to_lowercase();
|
||||
|
||||
if nu.email == SUPERADMIN_SECRET_EMAIL {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -1592,7 +1620,7 @@ async fn login(
|
||||
Json(Login { email, password }): Json<Login>,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let email = email.to_lowercase();
|
||||
let email_w_h: Option<(String, String, bool, bool)> = sqlx::query_as(
|
||||
"SELECT email, password_hash, super_admin, first_time_user FROM password WHERE email = $1 AND login_type = \
|
||||
'password'",
|
||||
@@ -1714,6 +1742,58 @@ async fn create_token(
|
||||
Ok((StatusCode::CREATED, token))
|
||||
}
|
||||
|
||||
async fn impersonate(
|
||||
Extension(db): Extension<DB>,
|
||||
Authed { email, username, .. }: Authed,
|
||||
Json(new_token): Json<NewToken>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let token = rd_string(30);
|
||||
let mut tx = db.begin().await?;
|
||||
require_super_admin(&mut tx, &email).await?;
|
||||
|
||||
if new_token.impersonate_email.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
"impersonate_username is required".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let impersonated = new_token.impersonate_email.unwrap();
|
||||
|
||||
let is_super_admin = sqlx::query_scalar!(
|
||||
"SELECT super_admin FROM password WHERE email = $1",
|
||||
impersonated
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
sqlx::query!(
|
||||
"INSERT INTO token
|
||||
(token, email, label, expiration, super_admin)
|
||||
VALUES ($1, $2, $3, $4, $5)",
|
||||
token,
|
||||
impersonated,
|
||||
new_token.label,
|
||||
new_token.expiration,
|
||||
is_super_admin
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&username,
|
||||
"users.impersonate",
|
||||
ActionKind::Delete,
|
||||
&"global",
|
||||
Some(&token[0..10]),
|
||||
Some([("impersonated", &format!("{impersonated}")[..])].into()),
|
||||
)
|
||||
.instrument(tracing::info_span!("token", email = &impersonated))
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok((StatusCode::CREATED, token))
|
||||
}
|
||||
|
||||
async fn list_tokens(
|
||||
Extension(db): Extension<DB>,
|
||||
Authed { email, .. }: Authed,
|
||||
@@ -1814,6 +1894,17 @@ pub async fn delete_expired_items_perdiodically(
|
||||
Err(e) => tracing::error!("Error deleting token: {}", e.to_string()),
|
||||
}
|
||||
|
||||
let pip_resolution_r = sqlx::query_scalar!(
|
||||
"DELETE FROM pip_resolution_cache WHERE expiration <= now() RETURNING hash",
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await;
|
||||
|
||||
match pip_resolution_r {
|
||||
Ok(res) => tracing::debug!("deleted {} pip_resolution: {:?}", res.len(), res),
|
||||
Err(e) => tracing::error!("Error deleting pip_resolution: {}", e.to_string()),
|
||||
}
|
||||
|
||||
let magic_links_deleted_r: std::result::Result<Vec<String>, _> = sqlx::query_scalar(
|
||||
"DELETE FROM magic_link WHERE expiration <= now()
|
||||
RETURNING concat(substring(token for 10), '*****')",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
oauth2::_refresh_token,
|
||||
users::{require_owner_of_path, Authed},
|
||||
users::{maybe_refresh_folders, require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
/*
|
||||
@@ -206,12 +206,15 @@ async fn check_path_conflict<'c>(
|
||||
|
||||
async fn create_variable(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(AlreadyEncrypted { already_encrypted }): Query<AlreadyEncrypted>,
|
||||
Json(variable): Json<CreateVariable>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let authed = maybe_refresh_folders(&variable.path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
check_path_conflict(&mut tx, &w_id, &variable.path).await?;
|
||||
@@ -330,6 +333,7 @@ async fn update_variable(
|
||||
use sql_builder::prelude::*;
|
||||
|
||||
let path = path.to_path();
|
||||
let authed = maybe_refresh_folders(&path, &w_id, authed, &db).await;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::time::Duration;
|
||||
|
||||
use serde::Serialize;
|
||||
use tokio::{select, sync::mpsc, time::interval};
|
||||
use windmill_common::METRICS_ENABLED;
|
||||
|
||||
use crate::db::DB;
|
||||
|
||||
@@ -89,9 +90,9 @@ impl WebhookShared {
|
||||
};
|
||||
let webook_opt = url_guard.value();
|
||||
if let Some(url) = webook_opt {
|
||||
let timer = WEBHOOK_REQUEST_COUNT.start_timer();
|
||||
let timer = if *METRICS_ENABLED { Some(WEBHOOK_REQUEST_COUNT.start_timer()) } else { None };
|
||||
let _ = client.post(url).json(&message).send().await;
|
||||
timer.stop_and_record();
|
||||
timer.map(|x| x.stop_and_record());
|
||||
drop(url_guard);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -944,10 +944,12 @@ async fn invite_user(
|
||||
Authed { username, is_admin, .. }: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(nu): Json<NewWorkspaceInvite>,
|
||||
Json(mut nu): Json<NewWorkspaceInvite>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
nu.email = nu.email.to_lowercase();
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -983,9 +985,10 @@ async fn add_user(
|
||||
Authed { username, is_admin, .. }: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(nu): Json<NewWorkspaceUser>,
|
||||
Json(mut nu): Json<NewWorkspaceUser>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
require_admin(is_admin, &username)?;
|
||||
nu.email = nu.email.to_lowercase();
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
if !VALID_USERNAME.is_match(&nu.username) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use error::Error;
|
||||
|
||||
@@ -30,7 +30,19 @@ pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 3;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref METRICS_ADDR: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
.ok()
|
||||
.map(|s| {
|
||||
s.parse::<bool>()
|
||||
.map(|b| b.then(|| SocketAddr::from(([0, 0, 0, 0], 8001))))
|
||||
.or_else(|_| s.parse::<SocketAddr>().map(Some))
|
||||
})
|
||||
.transpose().ok()
|
||||
.flatten()
|
||||
.flatten();
|
||||
pub static ref METRICS_ENABLED: bool = METRICS_ADDR.is_some();
|
||||
pub static ref BASE_URL: String = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
pub static ref IS_READY: Arc<std::sync::atomic::AtomicBool> = Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
@@ -58,14 +70,31 @@ pub async fn shutdown_signal(tx: tokio::sync::broadcast::Sender<()>) -> anyhow::
|
||||
pub async fn serve_metrics(
|
||||
addr: SocketAddr,
|
||||
mut rx: tokio::sync::broadcast::Receiver<()>,
|
||||
ready_worker_endpoint: bool,
|
||||
) -> Result<(), hyper::Error> {
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
axum::Server::bind(&addr)
|
||||
.serve(
|
||||
Router::new()
|
||||
.route("/metrics", get(metrics))
|
||||
.into_make_service(),
|
||||
use hyper::StatusCode;
|
||||
let router = Router::new().route("/metrics", get(metrics));
|
||||
|
||||
let router = if ready_worker_endpoint {
|
||||
router.route(
|
||||
"/ready",
|
||||
get(|| async {
|
||||
if IS_READY.load(Ordering::Relaxed) {
|
||||
(StatusCode::OK, "ready")
|
||||
} else {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "not ready")
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
router
|
||||
};
|
||||
|
||||
axum::Server::bind(&addr)
|
||||
.serve(router.into_make_service())
|
||||
.with_graceful_shutdown(async {
|
||||
rx.recv().await.ok();
|
||||
println!("Graceful shutdown of metrics");
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct Schedule {
|
||||
pub edited_by: String,
|
||||
pub edited_at: DateTime<chrono::Utc>,
|
||||
pub schedule: String,
|
||||
pub offset_: i32,
|
||||
pub timezone: String,
|
||||
pub enabled: bool,
|
||||
pub script_path: String,
|
||||
pub is_flow: bool,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
@@ -118,3 +119,9 @@ pub fn rd_string(len: usize) -> String {
|
||||
.map(char::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn calculate_hash(s: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(s);
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ serde_json.workspace = true
|
||||
ulid.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
hex.workspace = true
|
||||
reqwest.workspace = true
|
||||
lazy_static.workspace = true
|
||||
|
||||
@@ -22,6 +22,7 @@ use windmill_common::{
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue},
|
||||
scripts::{get_full_hub_script_by_path, HubScript, ScriptHash, ScriptLang},
|
||||
utils::StripPath,
|
||||
METRICS_ENABLED,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -146,7 +147,7 @@ pub async fn pull(
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
if job.is_some() {
|
||||
if job.is_some() && *METRICS_ENABLED {
|
||||
QUEUE_PULL_COUNT.inc();
|
||||
}
|
||||
|
||||
@@ -211,7 +212,9 @@ pub async fn delete_job(
|
||||
w_id: &str,
|
||||
job_id: Uuid,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
QUEUE_DELETE_COUNT.inc();
|
||||
if *METRICS_ENABLED {
|
||||
QUEUE_DELETE_COUNT.inc();
|
||||
}
|
||||
let job_removed = sqlx::query_scalar!(
|
||||
"DELETE FROM queue WHERE workspace_id = $1 AND id = $2 RETURNING 1",
|
||||
w_id,
|
||||
@@ -542,7 +545,9 @@ pub async fn push<'c>(
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("Could not insert into queue {job_id}: {e}")))?;
|
||||
// TODO: technically the job isn't queued yet, as the transaction can be rolled back. Should be solved when moving these metrics to the queue abstraction.
|
||||
QUEUE_PUSH_COUNT.inc();
|
||||
if *METRICS_ENABLED {
|
||||
QUEUE_PUSH_COUNT.inc();
|
||||
}
|
||||
|
||||
{
|
||||
let uuid_string = job_id.to_string();
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::Duration;
|
||||
use crate::{push, JobPayload};
|
||||
use sqlx::{query_scalar, Postgres, Transaction};
|
||||
use std::str::FromStr;
|
||||
use windmill_common::{
|
||||
error::{self, Result},
|
||||
schedule::Schedule,
|
||||
@@ -17,8 +16,6 @@ use windmill_common::{
|
||||
utils::{now_from_db, StripPath},
|
||||
};
|
||||
|
||||
use crate::{push, JobPayload};
|
||||
|
||||
pub async fn push_scheduled_job<'c>(
|
||||
mut tx: Transaction<'c, Postgres>,
|
||||
schedule: Schedule,
|
||||
@@ -26,13 +23,21 @@ pub async fn push_scheduled_job<'c>(
|
||||
let sched = cron::Schedule::from_str(&schedule.schedule)
|
||||
.map_err(|e| error::Error::BadRequest(e.to_string()))?;
|
||||
|
||||
let offset = Duration::minutes(schedule.offset_.into());
|
||||
let now = now_from_db(&mut tx).await?;
|
||||
let tz = chrono_tz::Tz::from_str(&schedule.timezone)
|
||||
.map_err(|e| error::Error::BadRequest(e.to_string()))?;
|
||||
|
||||
let now = now_from_db(&mut tx).await?.with_timezone(&tz);
|
||||
|
||||
let next = sched
|
||||
.after(&(now - offset + Duration::seconds(1)))
|
||||
.after(&now)
|
||||
.next()
|
||||
.expect("a schedule should have a next event")
|
||||
+ offset;
|
||||
.expect("a schedule should have a next event");
|
||||
|
||||
// println!("next event ({:?}): {}", tz, next);
|
||||
// println!("next event(UTC): {}", next.with_timezone(&chrono::Utc));
|
||||
|
||||
// Scheduled events must be stored in the database in UTC
|
||||
let next = next.with_timezone(&chrono::Utc);
|
||||
|
||||
let already_exists: bool = query_scalar!(
|
||||
"SELECT EXISTS (SELECT 1 FROM queue WHERE workspace_id = $1 AND schedule_path = $2 AND scheduled_for = $3)",
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
name: "deno run script"
|
||||
|
||||
mode: ONCE
|
||||
hostname: "deno"
|
||||
log_level: ERROR
|
||||
|
||||
rlimit_as: 16000
|
||||
rlimit_cpu: 1000
|
||||
rlimit_fsize: 1000
|
||||
rlimit_nofile: 10000
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
|
||||
clone_newnet: false
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
keep_caps: false
|
||||
keep_env: true
|
||||
mount_proc: true
|
||||
|
||||
mount {
|
||||
src: "/bin"
|
||||
dst: "/bin"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/lib"
|
||||
dst: "/lib"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "/lib64"
|
||||
dst: "/lib64"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "/usr"
|
||||
dst: "/usr"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/null"
|
||||
dst: "/dev/null"
|
||||
is_bind: true
|
||||
rw: true
|
||||
}
|
||||
|
||||
mount {
|
||||
dst: "/tmp"
|
||||
fstype: "tmpfs"
|
||||
rw: true
|
||||
options: "size=500000000"
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/wrapper.ts"
|
||||
dst: "/tmp/wrapper.ts"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/main.ts"
|
||||
dst: "/tmp/main.ts"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/import_map.json"
|
||||
dst: "/tmp/import_map.json"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/args.json"
|
||||
dst: "/tmp/args.json"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/result.json"
|
||||
dst: "/tmp/result.json"
|
||||
is_bind: true
|
||||
rw: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/etc"
|
||||
dst: "/etc"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/random"
|
||||
dst: "/dev/random"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/urandom"
|
||||
dst: "/dev/urandom"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{CACHE_DIR}"
|
||||
dst: "/tmp/.cache/deno"
|
||||
is_bind: true
|
||||
rw: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
{SHARED_MOUNT}
|
||||
|
||||
iface_no_lo: true
|
||||
|
||||
envar: "DENO_DIR=/tmp/.cache/deno"
|
||||
envar: "NO_COLOR=true"
|
||||
envar: "HOME=/tmp"
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,14 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use tracing::instrument;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{error::Error, flow_status::FlowStatusModule, schedule::Schedule};
|
||||
use windmill_common::{
|
||||
error::Error, flow_status::FlowStatusModule, schedule::Schedule, METRICS_ENABLED,
|
||||
};
|
||||
use windmill_queue::{delete_job, schedule::get_schedule_opt, JobKind, QueuedJob, CLOUD_HOSTED};
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
@@ -20,7 +24,9 @@ pub async fn add_completed_job_error(
|
||||
e: serde_json::Value,
|
||||
metrics: Option<crate::worker::Metrics>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
metrics.map(|m| m.worker_execution_failed.inc());
|
||||
if *METRICS_ENABLED {
|
||||
metrics.map(|m| m.worker_execution_failed.inc());
|
||||
}
|
||||
let result = serde_json::json!({ "error": e });
|
||||
let _ = add_completed_job(db, &queued_job, false, false, result.clone(), logs).await?;
|
||||
Ok(result)
|
||||
@@ -153,6 +159,8 @@ pub async fn add_completed_job(
|
||||
.execute(&mut tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("Could not add completed job {job_id}: {e}")))?;
|
||||
println!("{:?}", SystemTime::now());
|
||||
|
||||
let _ = delete_job(db, &queued_job.workspace_id, job_id).await?;
|
||||
if !queued_job.is_flow_step
|
||||
&& queued_job.job_kind != JobKind::Flow
|
||||
@@ -232,7 +240,7 @@ pub async fn schedule_again_if_scheduled<'c>(
|
||||
edited_by: schedule.edited_by,
|
||||
edited_at: schedule.edited_at,
|
||||
schedule: schedule.schedule,
|
||||
offset_: schedule.offset_,
|
||||
timezone: schedule.timezone,
|
||||
enabled: schedule.enabled,
|
||||
script_path: schedule.script_path,
|
||||
is_flow: schedule.is_flow,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,6 @@ use windmill_queue::{
|
||||
canceled_job_to_result, get_queued_job, push, JobPayload, QueuedJob, RawCode,
|
||||
};
|
||||
|
||||
#[async_recursion]
|
||||
// #[instrument(level = "trace", skip_all)]
|
||||
pub async fn update_flow_status_after_job_completion(
|
||||
db: &DB,
|
||||
@@ -51,57 +50,125 @@ pub async fn update_flow_status_after_job_completion(
|
||||
stop_early_override: Option<bool>,
|
||||
base_internal_url: &str,
|
||||
) -> error::Result<()> {
|
||||
tracing::debug!("UPDATE FLOW STATUS: {flow:?} {success} {result:?} {w_id}");
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let old_status_json = sqlx::query_scalar!(
|
||||
"SELECT flow_status FROM queue WHERE id = $1 AND workspace_id = $2",
|
||||
// this is manual tailrecursion because async_recursion blows up the stack
|
||||
let mut depth = 0;
|
||||
let mut rec = update_flow_status_after_job_completion_internal(
|
||||
db,
|
||||
client,
|
||||
flow,
|
||||
w_id
|
||||
job_id_for_status,
|
||||
w_id,
|
||||
success,
|
||||
result,
|
||||
metrics.clone(),
|
||||
unrecoverable,
|
||||
same_worker_tx.clone(),
|
||||
worker_dir,
|
||||
stop_early_override,
|
||||
base_internal_url,
|
||||
depth,
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"fetching flow status {flow} while reporting {success} {result:?}: {e}"
|
||||
))
|
||||
})?
|
||||
.ok_or_else(|| Error::InternalErr(format!("requiring a previous status")))?;
|
||||
.await?;
|
||||
while let Some(nrec) = rec {
|
||||
depth += 1;
|
||||
rec = update_flow_status_after_job_completion_internal(
|
||||
db,
|
||||
client,
|
||||
nrec.flow,
|
||||
&nrec.job_id_for_status,
|
||||
w_id,
|
||||
nrec.success,
|
||||
nrec.result,
|
||||
metrics.clone(),
|
||||
false,
|
||||
same_worker_tx.clone(),
|
||||
worker_dir,
|
||||
nrec.stop_early_override,
|
||||
base_internal_url,
|
||||
depth,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub struct RecUpdateFlowStatusAfterJobCompletion {
|
||||
flow: uuid::Uuid,
|
||||
job_id_for_status: Uuid,
|
||||
success: bool,
|
||||
result: serde_json::Value,
|
||||
stop_early_override: Option<bool>,
|
||||
}
|
||||
// #[instrument(level = "trace", skip_all)]
|
||||
pub async fn update_flow_status_after_job_completion_internal(
|
||||
db: &DB,
|
||||
client: &AuthedClient,
|
||||
flow: uuid::Uuid,
|
||||
job_id_for_status: &Uuid,
|
||||
w_id: &str,
|
||||
success: bool,
|
||||
result: serde_json::Value,
|
||||
metrics: Option<worker::Metrics>,
|
||||
unrecoverable: bool,
|
||||
same_worker_tx: Sender<Uuid>,
|
||||
worker_dir: &str,
|
||||
stop_early_override: Option<bool>,
|
||||
base_internal_url: &str,
|
||||
depth: u8,
|
||||
) -> error::Result<Option<RecUpdateFlowStatusAfterJobCompletion>> {
|
||||
let (should_continue_flow, flow_job, stop_early, skip_if_stop_early, nresult) = {
|
||||
tracing::debug!("UPDATE FLOW STATUS: {flow:?} {success} {result:?} {w_id} {depth}");
|
||||
|
||||
let old_status = serde_json::from_value::<FlowStatus>(old_status_json)
|
||||
.ok()
|
||||
.ok_or_else(|| {
|
||||
Error::InternalErr(format!("requiring status to be parsabled as FlowStatus"))
|
||||
})?;
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let module_index = usize::try_from(old_status.step).ok();
|
||||
let old_status_json = sqlx::query_scalar!(
|
||||
"SELECT flow_status FROM queue WHERE id = $1 AND workspace_id = $2",
|
||||
flow,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::InternalErr(format!(
|
||||
"fetching flow status {flow} while reporting {success} {result:?}: {e}"
|
||||
))
|
||||
})?
|
||||
.ok_or_else(|| Error::InternalErr(format!("requiring a previous status")))?;
|
||||
|
||||
let module_status = module_index
|
||||
.and_then(|i| old_status.modules.get(i))
|
||||
.unwrap_or(&old_status.failure_module.module_status);
|
||||
let old_status = serde_json::from_value::<FlowStatus>(old_status_json)
|
||||
.ok()
|
||||
.ok_or_else(|| {
|
||||
Error::InternalErr(format!("requiring status to be parsabled as FlowStatus"))
|
||||
})?;
|
||||
|
||||
tracing::debug!("UPDATE FLOW STATUS 2: {module_index:#?} {module_status:#?} {old_status:#?} ");
|
||||
let module_index = usize::try_from(old_status.step).ok();
|
||||
|
||||
let skip_loop_failures = if matches!(
|
||||
module_status,
|
||||
FlowStatusModule::InProgress { iterator: Some(_), .. }
|
||||
) {
|
||||
compute_skip_loop_failures(flow, old_status.step, &mut tx)
|
||||
.await?
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let module_status = module_index
|
||||
.and_then(|i| old_status.modules.get(i))
|
||||
.unwrap_or(&old_status.failure_module.module_status);
|
||||
|
||||
let is_failure_step = old_status.step >= old_status.modules.len() as i32;
|
||||
tracing::debug!(
|
||||
"UPDATE FLOW STATUS 2: {module_index:#?} {module_status:#?} {old_status:#?} "
|
||||
);
|
||||
|
||||
let (mut stop_early, skip_if_stop_early) = if let Some(se) = stop_early_override {
|
||||
(true, se)
|
||||
} else if is_failure_step {
|
||||
(false, false)
|
||||
} else {
|
||||
let r = sqlx::query!(
|
||||
let skip_loop_failures = if matches!(
|
||||
module_status,
|
||||
FlowStatusModule::InProgress { iterator: Some(_), .. }
|
||||
) {
|
||||
compute_skip_loop_failures(flow, old_status.step, &mut tx)
|
||||
.await?
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let is_failure_step = old_status.step >= old_status.modules.len() as i32;
|
||||
|
||||
let (mut stop_early, skip_if_stop_early) = if let Some(se) = stop_early_override {
|
||||
(true, se)
|
||||
} else if is_failure_step {
|
||||
(false, false)
|
||||
} else {
|
||||
let r = sqlx::query!(
|
||||
"
|
||||
SELECT raw_flow->'modules'->$1::int->'stop_after_if'->>'expr' as stop_early_expr,
|
||||
(raw_flow->'modules'->$1::int->'stop_after_if'->>'skip_if_stopped')::bool as skip_if_stopped,
|
||||
@@ -116,37 +183,39 @@ pub async fn update_flow_status_after_job_completion(
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("retrieval of stop_early_expr from state: {e}")))?;
|
||||
|
||||
let stop_early = success
|
||||
&& if let Some(expr) = r.stop_early_expr.clone() {
|
||||
compute_bool_from_expr(expr, &r.args, result.clone(), None, Some(client)).await?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
(stop_early, r.skip_if_stopped.unwrap_or(false))
|
||||
};
|
||||
let stop_early = success
|
||||
&& if let Some(expr) = r.stop_early_expr.clone() {
|
||||
compute_bool_from_expr(expr, &r.args, result.clone(), None, Some(client))
|
||||
.await?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
(stop_early, r.skip_if_stopped.unwrap_or(false))
|
||||
};
|
||||
|
||||
let skip_branch_failure = match module_status {
|
||||
FlowStatusModule::InProgress {
|
||||
branchall: Some(BranchAllStatus { branch, .. }), ..
|
||||
} => compute_skip_branchall_failure(flow, old_status.step, *branch, &mut tx)
|
||||
.await?
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
};
|
||||
let skip_branch_failure = match module_status {
|
||||
FlowStatusModule::InProgress {
|
||||
branchall: Some(BranchAllStatus { branch, .. }),
|
||||
..
|
||||
} => compute_skip_branchall_failure(flow, old_status.step, *branch, &mut tx)
|
||||
.await?
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let skip_failure = skip_branch_failure || skip_loop_failures;
|
||||
let skip_failure = skip_branch_failure || skip_loop_failures;
|
||||
|
||||
let (inc_step_counter, new_status) = match module_status {
|
||||
FlowStatusModule::InProgress {
|
||||
iterator,
|
||||
branchall,
|
||||
parallel,
|
||||
flow_jobs: Some(jobs),
|
||||
..
|
||||
} if *parallel => {
|
||||
let (nindex, len) = match (iterator, branchall) {
|
||||
(Some(Iterator { itered, .. }), _) => {
|
||||
let nindex = sqlx::query_scalar!(
|
||||
let (inc_step_counter, new_status) = match module_status {
|
||||
FlowStatusModule::InProgress {
|
||||
iterator,
|
||||
branchall,
|
||||
parallel,
|
||||
flow_jobs: Some(jobs),
|
||||
..
|
||||
} if *parallel => {
|
||||
let (nindex, len) = match (iterator, branchall) {
|
||||
(Some(Iterator { itered, .. }), _) => {
|
||||
let nindex = sqlx::query_scalar!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'iterator', 'index'], ((flow_status->'modules'->$1::int->'iterator'->>'index')::int + 1)::text::jsonb)
|
||||
@@ -159,10 +228,10 @@ pub async fn update_flow_status_after_job_completion(
|
||||
.fetch_one(&mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("requiring an index in InProgress")))?;
|
||||
(nindex, itered.len() as i32)
|
||||
}
|
||||
(_, Some(BranchAllStatus { len, .. })) => {
|
||||
let nindex = sqlx::query_scalar!(
|
||||
(nindex, itered.len() as i32)
|
||||
}
|
||||
(_, Some(BranchAllStatus { len, .. })) => {
|
||||
let nindex = sqlx::query_scalar!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT, 'branchall', 'branch'], ((flow_status->'modules'->$1::int->'branchall'->>'branch')::int + 1)::text::jsonb)
|
||||
@@ -175,269 +244,292 @@ pub async fn update_flow_status_after_job_completion(
|
||||
.fetch_one(&mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("requiring an index in InProgress")))?;
|
||||
(nindex, *len as i32)
|
||||
}
|
||||
_ => Err(Error::InternalErr(format!(
|
||||
"unexpected status for parallel module"
|
||||
)))?,
|
||||
};
|
||||
if nindex == len {
|
||||
let new_status = if skip_loop_failures
|
||||
|| sqlx::query_scalar!(
|
||||
"
|
||||
(nindex, *len as i32)
|
||||
}
|
||||
_ => Err(Error::InternalErr(format!(
|
||||
"unexpected status for parallel module"
|
||||
)))?,
|
||||
};
|
||||
if nindex == len {
|
||||
let new_status = if skip_loop_failures
|
||||
|| sqlx::query_scalar!(
|
||||
"
|
||||
SELECT success
|
||||
FROM completed_job
|
||||
WHERE id = ANY($1)
|
||||
",
|
||||
jobs.as_slice(),
|
||||
)
|
||||
.fetch_all(&mut tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.all(|x| x)
|
||||
{
|
||||
FlowStatusModule::Success {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs: Some(jobs.clone()),
|
||||
branch_chosen: None,
|
||||
approvers: vec![],
|
||||
}
|
||||
jobs.as_slice(),
|
||||
)
|
||||
.fetch_all(&mut tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.all(|x| x)
|
||||
{
|
||||
FlowStatusModule::Success {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs: Some(jobs.clone()),
|
||||
branch_chosen: None,
|
||||
approvers: vec![],
|
||||
}
|
||||
} else {
|
||||
FlowStatusModule::Failure {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs: Some(jobs.clone()),
|
||||
branch_chosen: None,
|
||||
}
|
||||
};
|
||||
(true, Some(new_status))
|
||||
} else {
|
||||
FlowStatusModule::Failure {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs: Some(jobs.clone()),
|
||||
branch_chosen: None,
|
||||
}
|
||||
};
|
||||
(true, Some(new_status))
|
||||
} else {
|
||||
tx.commit().await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
FlowStatusModule::InProgress {
|
||||
iterator: Some(windmill_common::flow_status::Iterator { index, itered, .. }),
|
||||
..
|
||||
} if (*index + 1 < itered.len() && (success || skip_loop_failures)) && !stop_early => {
|
||||
(false, None)
|
||||
}
|
||||
FlowStatusModule::InProgress {
|
||||
branchall: Some(BranchAllStatus { branch, len, .. }),
|
||||
..
|
||||
} if branch.to_owned() < len - 1 && (success || skip_branch_failure) => (false, None),
|
||||
_ => {
|
||||
if stop_early
|
||||
&& matches!(
|
||||
module_status,
|
||||
FlowStatusModule::InProgress { iterator: Some(_), .. }
|
||||
)
|
||||
{
|
||||
// if we're stopping early inside a loop, we just want to break the loop instead
|
||||
stop_early = false;
|
||||
}
|
||||
let (flow_jobs, branch_chosen) = match module_status {
|
||||
FlowStatusModule::InProgress { flow_jobs, branch_chosen, .. } => {
|
||||
(flow_jobs.clone(), branch_chosen.clone())
|
||||
tx.commit().await?;
|
||||
return Ok(None);
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
if success || (flow_jobs.is_some() && (skip_loop_failures || skip_branch_failure)) {
|
||||
(
|
||||
true,
|
||||
Some(FlowStatusModule::Success {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs,
|
||||
branch_chosen,
|
||||
approvers: vec![],
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
false,
|
||||
Some(FlowStatusModule::Failure {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs,
|
||||
branch_chosen,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
FlowStatusModule::InProgress {
|
||||
iterator: Some(windmill_common::flow_status::Iterator { index, itered, .. }),
|
||||
..
|
||||
} if (*index + 1 < itered.len() && (success || skip_loop_failures)) && !stop_early => {
|
||||
(false, None)
|
||||
}
|
||||
FlowStatusModule::InProgress {
|
||||
branchall: Some(BranchAllStatus { branch, len, .. }),
|
||||
..
|
||||
} if branch.to_owned() < len - 1 && (success || skip_branch_failure) => (false, None),
|
||||
_ => {
|
||||
if stop_early
|
||||
&& matches!(
|
||||
module_status,
|
||||
FlowStatusModule::InProgress { iterator: Some(_), .. }
|
||||
)
|
||||
{
|
||||
// if we're stopping early inside a loop, we just want to break the loop instead
|
||||
stop_early = false;
|
||||
}
|
||||
let (flow_jobs, branch_chosen) = match module_status {
|
||||
FlowStatusModule::InProgress { flow_jobs, branch_chosen, .. } => {
|
||||
(flow_jobs.clone(), branch_chosen.clone())
|
||||
}
|
||||
_ => (None, None),
|
||||
};
|
||||
if success || (flow_jobs.is_some() && (skip_loop_failures || skip_branch_failure)) {
|
||||
(
|
||||
true,
|
||||
Some(FlowStatusModule::Success {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs,
|
||||
branch_chosen,
|
||||
approvers: vec![],
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
false,
|
||||
Some(FlowStatusModule::Failure {
|
||||
id: module_status.id(),
|
||||
job: job_id_for_status.clone(),
|
||||
flow_jobs,
|
||||
branch_chosen,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let step_counter = if inc_step_counter {
|
||||
sqlx::query!(
|
||||
"
|
||||
let step_counter = if inc_step_counter {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['step'], $1)
|
||||
WHERE id = $2
|
||||
",
|
||||
json!(old_status.step + 1),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
old_status.step + 1
|
||||
} else {
|
||||
old_status.step
|
||||
};
|
||||
json!(old_status.step + 1),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
old_status.step + 1
|
||||
} else {
|
||||
old_status.step
|
||||
};
|
||||
|
||||
/* is_last_step is true when the step_counter (the next step index) is an invalid index */
|
||||
let is_last_step = usize::try_from(step_counter)
|
||||
.map(|i| !(..old_status.modules.len()).contains(&i))
|
||||
.unwrap_or(true);
|
||||
/* is_last_step is true when the step_counter (the next step index) is an invalid index */
|
||||
let is_last_step = usize::try_from(step_counter)
|
||||
.map(|i| !(..old_status.modules.len()).contains(&i))
|
||||
.unwrap_or(true);
|
||||
|
||||
if let Some(new_status) = new_status.as_ref() {
|
||||
if is_failure_step {
|
||||
let parent_module = sqlx::query_scalar!(
|
||||
if let Some(new_status) = new_status.as_ref() {
|
||||
if is_failure_step {
|
||||
let parent_module = sqlx::query_scalar!(
|
||||
"SELECT flow_status->'failure_module'->>'parent_module' FROM queue WHERE id = $1",
|
||||
flow
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['failure_module'], $1)
|
||||
WHERE id = $2
|
||||
",
|
||||
json!(FlowStatusModuleWParent { parent_module, module_status: new_status.clone() }),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)
|
||||
WHERE id = $3
|
||||
",
|
||||
old_status.step.to_string(),
|
||||
json!(new_status),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(job_result) = new_status.job_result() {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET leaf_jobs = JSONB_SET(coalesce(leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)
|
||||
WHERE COALESCE((SELECT root_job FROM queue WHERE id = $3), $3) = id
|
||||
",
|
||||
new_status.id(),
|
||||
json!(job_result),
|
||||
json!(FlowStatusModuleWParent {
|
||||
parent_module,
|
||||
module_status: new_status.clone()
|
||||
}),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2)
|
||||
WHERE id = $3
|
||||
",
|
||||
old_status.step.to_string(),
|
||||
json!(new_status),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(job_result) = new_status.job_result() {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
SET leaf_jobs = JSONB_SET(coalesce(leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)
|
||||
WHERE COALESCE((SELECT root_job FROM queue WHERE id = $3), $3) = id
|
||||
",
|
||||
new_status.id(),
|
||||
json!(job_result),
|
||||
flow
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = match &new_status {
|
||||
Some(FlowStatusModule::Success { flow_jobs: Some(jobs), .. })
|
||||
| Some(FlowStatusModule::Failure { flow_jobs: Some(jobs), .. }) => {
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
let nresult = match &new_status {
|
||||
Some(FlowStatusModule::Success { flow_jobs: Some(jobs), .. })
|
||||
| Some(FlowStatusModule::Failure { flow_jobs: Some(jobs), .. }) => {
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT result, id
|
||||
FROM completed_job
|
||||
WHERE id = ANY($1)
|
||||
AND workspace_id = $2
|
||||
",
|
||||
jobs.as_slice(),
|
||||
w_id
|
||||
)
|
||||
.fetch_all(&mut tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|r| (r.id, r.result))
|
||||
.collect::<HashMap<_, _>>();
|
||||
jobs.as_slice(),
|
||||
w_id
|
||||
)
|
||||
.fetch_all(&mut tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|r| (r.id, r.result))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let results = jobs
|
||||
.iter()
|
||||
.map(|j| {
|
||||
results
|
||||
.get(j)
|
||||
.ok_or_else(|| Error::InternalErr(format!("missing job result for {}", j)))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let results = jobs
|
||||
.iter()
|
||||
.map(|j| {
|
||||
results.get(j).ok_or_else(|| {
|
||||
Error::InternalErr(format!("missing job result for {}", j))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
json!(results)
|
||||
}
|
||||
_ => result,
|
||||
};
|
||||
json!(results)
|
||||
}
|
||||
_ => result,
|
||||
};
|
||||
|
||||
if matches!(&new_status, Some(FlowStatusModule::Success { .. })) {
|
||||
sqlx::query(
|
||||
"
|
||||
if matches!(&new_status, Some(FlowStatusModule::Success { .. })) {
|
||||
sqlx::query(
|
||||
"
|
||||
UPDATE queue
|
||||
SET flow_status = flow_status - 'retry'
|
||||
WHERE id = $1
|
||||
RETURNING flow_status
|
||||
",
|
||||
)
|
||||
.bind(flow)
|
||||
.execute(&mut tx)
|
||||
.await
|
||||
.context("remove flow status retry")?;
|
||||
}
|
||||
|
||||
let flow_job = get_queued_job(flow, w_id, &mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("requiring flow to be in the queue")))?;
|
||||
|
||||
let job_root = flow_job
|
||||
.root_job
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
tracing::info!(id = %flow_job.id, root_id = %job_root, "update flow status");
|
||||
|
||||
let raw_flow = flow_job.parse_raw_flow();
|
||||
let module = raw_flow.as_ref().and_then(|module| {
|
||||
module_index.and_then(|i| module.modules.get(i).or(module.failure_module.as_ref()))
|
||||
});
|
||||
|
||||
let should_continue_flow = match success {
|
||||
_ if stop_early => false,
|
||||
_ if flow_job.canceled => false,
|
||||
true => !is_last_step,
|
||||
false if unrecoverable => false,
|
||||
false if skip_failure => !is_last_step,
|
||||
false
|
||||
if next_retry(
|
||||
&module.and_then(|m| m.retry.clone()).unwrap_or_default(),
|
||||
&old_status.retry,
|
||||
)
|
||||
.is_some() =>
|
||||
{
|
||||
true
|
||||
.bind(flow)
|
||||
.execute(&mut tx)
|
||||
.await
|
||||
.context("remove flow status retry")?;
|
||||
}
|
||||
false if has_failure_module(flow, &mut tx).await? && !is_failure_step => true,
|
||||
false => false,
|
||||
};
|
||||
|
||||
if old_status.step == 0
|
||||
&& !flow_job.is_flow_step
|
||||
&& flow_job.schedule_path.is_some()
|
||||
&& flow_job.script_path.is_some()
|
||||
{
|
||||
tx = schedule_again_if_scheduled(
|
||||
tx,
|
||||
db,
|
||||
flow_job.schedule_path.as_ref().unwrap(),
|
||||
flow_job.script_path.as_ref().unwrap(),
|
||||
&w_id,
|
||||
let flow_job = get_queued_job(flow, w_id, &mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("requiring flow to be in the queue")))?;
|
||||
|
||||
let job_root = flow_job
|
||||
.root_job
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
tracing::info!(id = %flow_job.id, root_id = %job_root, "update flow status");
|
||||
|
||||
let module = {
|
||||
let raw_flow = flow_job.parse_raw_flow();
|
||||
if let Some(raw_flow) = raw_flow {
|
||||
if let Some(i) = module_index {
|
||||
if let Some(module) = raw_flow.modules.get(i) {
|
||||
Some(module.clone())
|
||||
} else {
|
||||
raw_flow.failure_module
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let should_continue_flow = match success {
|
||||
_ if stop_early => false,
|
||||
_ if flow_job.canceled => false,
|
||||
true => !is_last_step,
|
||||
false if unrecoverable => false,
|
||||
false if skip_failure => !is_last_step,
|
||||
false
|
||||
if next_retry(
|
||||
&module.and_then(|m| m.retry.clone()).unwrap_or_default(),
|
||||
&old_status.retry,
|
||||
)
|
||||
.is_some() =>
|
||||
{
|
||||
true
|
||||
}
|
||||
false if has_failure_module(flow, &mut tx).await? && !is_failure_step => true,
|
||||
false => false,
|
||||
};
|
||||
|
||||
if old_status.step == 0
|
||||
&& !flow_job.is_flow_step
|
||||
&& flow_job.schedule_path.is_some()
|
||||
&& flow_job.script_path.is_some()
|
||||
{
|
||||
tx = schedule_again_if_scheduled(
|
||||
tx,
|
||||
db,
|
||||
flow_job.schedule_path.as_ref().unwrap(),
|
||||
flow_job.script_path.as_ref().unwrap(),
|
||||
&w_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
(
|
||||
should_continue_flow,
|
||||
flow_job,
|
||||
stop_early,
|
||||
skip_if_stop_early,
|
||||
nresult,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
};
|
||||
|
||||
let done = if !should_continue_flow {
|
||||
let logs = if flow_job.canceled {
|
||||
@@ -464,7 +556,7 @@ pub async fn update_flow_status_after_job_completion(
|
||||
&flow_job,
|
||||
success,
|
||||
stop_early && skip_if_stop_early,
|
||||
result.clone(),
|
||||
nresult.clone(),
|
||||
logs,
|
||||
)
|
||||
.await?;
|
||||
@@ -475,7 +567,7 @@ pub async fn update_flow_status_after_job_completion(
|
||||
&flow_job,
|
||||
db,
|
||||
client,
|
||||
result.clone(),
|
||||
nresult.clone(),
|
||||
same_worker_tx.clone(),
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
@@ -503,30 +595,22 @@ pub async fn update_flow_status_after_job_completion(
|
||||
}
|
||||
|
||||
if let Some(parent_job) = flow_job.parent_job {
|
||||
return Ok(update_flow_status_after_job_completion(
|
||||
db,
|
||||
client,
|
||||
parent_job,
|
||||
&flow,
|
||||
w_id,
|
||||
return Ok(Some(RecUpdateFlowStatusAfterJobCompletion {
|
||||
flow: parent_job,
|
||||
job_id_for_status: flow,
|
||||
success,
|
||||
result,
|
||||
metrics,
|
||||
false,
|
||||
same_worker_tx.clone(),
|
||||
worker_dir,
|
||||
if stop_early {
|
||||
result: nresult,
|
||||
stop_early_override: if stop_early {
|
||||
Some(skip_if_stop_early)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
base_internal_url,
|
||||
)
|
||||
.await?);
|
||||
}));
|
||||
}
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn compute_skip_loop_failures<'c>(
|
||||
@@ -1261,7 +1345,11 @@ async fn push_next_flow_job(
|
||||
Ok(v) => (Some(v), None),
|
||||
Err(e) => (None, Some(e)),
|
||||
};
|
||||
let root_job = flow_job.root_job.or_else(|| Some(flow_job.id));
|
||||
let root_job = if matches!(module.value, FlowModuleValue::Flow { .. }) {
|
||||
None
|
||||
} else {
|
||||
flow_job.root_job.or_else(|| Some(flow_job.id))
|
||||
};
|
||||
let (uuid, inner_tx) = push(
|
||||
tx,
|
||||
&flow_job.workspace_id,
|
||||
|
||||
@@ -19,7 +19,7 @@ import sync from "./sync.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
export const VERSION = "v1.84.0";
|
||||
export const VERSION = "v1.86.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
|
||||
45
cli/sync.ts
45
cli/sync.ts
@@ -335,21 +335,15 @@ async function pull(
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
if (change.path.endsWith(".json")) {
|
||||
const diffs = microdiff(
|
||||
JSON.parse(change.before),
|
||||
JSON.parse(change.after),
|
||||
{ cyclesFix: false }
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`
|
||||
);
|
||||
await applyDiff(diffs, target);
|
||||
if (!change.path.endsWith(".json")) {
|
||||
console.log(`Editing script content of ${change.path}`);
|
||||
} else {
|
||||
console.log(`Editing script ${change.path}`);
|
||||
await Deno.writeTextFile(target, change.after);
|
||||
console.log(
|
||||
`Editing ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
}
|
||||
await Deno.writeTextFile(target, change.after);
|
||||
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
await Deno.copyFile(target, stateTarget);
|
||||
@@ -425,31 +419,6 @@ async function pull(
|
||||
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m");
|
||||
console.log();
|
||||
}
|
||||
async function applyDiff(diffs: Difference[], file: string) {
|
||||
ensureDir(path.dirname(file));
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(await Deno.readTextFile(file));
|
||||
} catch {
|
||||
json = {};
|
||||
}
|
||||
// TODO: Delegate the below to the object itself
|
||||
// This would work by infering the type of `JSON` (which includes then statically typing it using decoverto) and then
|
||||
// delegating the applying of the diffs to the object via an interface
|
||||
for (const diff of diffs) {
|
||||
if (diff.type === "CREATE") {
|
||||
setValueByPath(json, diff.path, diff.value);
|
||||
} else if (diff.type === "REMOVE") {
|
||||
setValueByPath(json, diff.path, undefined);
|
||||
} else if (diff.type === "CHANGE") {
|
||||
setValueByPath(json, diff.path, diff.value);
|
||||
}
|
||||
}
|
||||
|
||||
await Deno.writeTextFile(file, JSON.stringify(json, undefined, " "), {
|
||||
create: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function prettyChanges(changes: Change[]) {
|
||||
|
||||
@@ -80,6 +80,31 @@ export async function getResource(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full resource value by path
|
||||
* @param path path of the resource, default to internal state path
|
||||
* @param undefinedIfEmpty if the resource does not exist, return undefined instead of throwing an error
|
||||
* @returns full resource
|
||||
*/
|
||||
export async function getFullResource(
|
||||
path?: string,
|
||||
undefinedIfEmpty?: boolean
|
||||
): Promise<any> {
|
||||
const workspace = getWorkspace();
|
||||
path = path ?? getStatePath();
|
||||
try {
|
||||
const resource = await ResourceService.getResource({ workspace, path });
|
||||
const value = await _transformLeaf(resource.value);
|
||||
return { ...resource, value };
|
||||
} catch (e: any) {
|
||||
if (undefinedIfEmpty && e.status === 404) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw Error(`Resource not found at ${path} or not visible to you`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getStatePath(): string {
|
||||
const state_path = Deno.env.get("WM_STATE_PATH");
|
||||
if (state_path === undefined) {
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
version: '3.7'
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
|
||||
db:
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
# NOTE: if port 5432 are already in use locally, we'll also bind another port
|
||||
- 5433:5432
|
||||
expose:
|
||||
- 5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: windmill
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -25,11 +22,11 @@ services:
|
||||
deploy:
|
||||
replicas: 1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
expose:
|
||||
- 8000
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
|
||||
- BASE_URL=http://${WM_BASE_URL}
|
||||
- BASE_URL=${WM_BASE_URL}
|
||||
- RUST_LOG=info
|
||||
## You can set the number of workers to > 0 and not need any separate worker service
|
||||
- NUM_WORKERS=0
|
||||
@@ -39,8 +36,8 @@ services:
|
||||
db:
|
||||
condition: service_healthy
|
||||
# volumes:
|
||||
# - ./oauth.json/:/usr/src/app/oauth.json
|
||||
|
||||
# - ./oauth.json/:/usr/src/app/oauth.json
|
||||
|
||||
windmill_worker:
|
||||
image: ghcr.io/windmill-labs/windmill:main
|
||||
deploy:
|
||||
@@ -48,7 +45,7 @@ services:
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
|
||||
- BASE_URL=http://${WM_BASE_URL}
|
||||
- BASE_URL=${WM_BASE_URL}
|
||||
- BASE_INTERNAL_URL=http://windmill_server:8000
|
||||
- RUST_LOG=info
|
||||
- NUM_WORKERS=1
|
||||
@@ -67,8 +64,8 @@ services:
|
||||
lsp:
|
||||
image: ghcr.io/windmill-labs/windmill-lsp:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3001:3001
|
||||
expose:
|
||||
- 3001
|
||||
|
||||
caddy:
|
||||
image: caddy:2.5.2-alpine
|
||||
@@ -79,7 +76,7 @@ services:
|
||||
- 80:80
|
||||
- 443:443
|
||||
environment:
|
||||
- BASE_URL=${WM_BASE_URL}
|
||||
- BASE_URL=${CADDY_REVERSE_PROXY}
|
||||
|
||||
volumes:
|
||||
db_data: null
|
||||
|
||||
@@ -2,9 +2,11 @@ http://localhost {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /api/* http://localhost:8000
|
||||
reverse_proxy /* http://localhost:3000
|
||||
reverse_proxy /ws/* http://localhost:3001
|
||||
}
|
||||
|
||||
https://localhost {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /api/* http://localhost:8000
|
||||
reverse_proxy /ws/* http://localhost:3001
|
||||
}
|
||||
|
||||
1
frontend/copy_workers.sh
Executable file
1
frontend/copy_workers.sh
Executable file
@@ -0,0 +1 @@
|
||||
cp -r node_modules/monaco-editor-workers/dist/workers static
|
||||
5457
frontend/package-lock.json
generated
5457
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.84.0",
|
||||
"version": "1.86.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -16,7 +16,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.31.1",
|
||||
"@sveltejs/adapter-static": "^1.0.0",
|
||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||
"@sveltejs/adapter-static": "^2.0.1",
|
||||
"@sveltejs/kit": "^1.0.0-next.589",
|
||||
"@sveltejs/package": "^1.0.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -25,16 +26,17 @@
|
||||
"@types/d3-zoom": "^3.0.2",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@types/vscode": "~1.76.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cssnano": "^5.1.14",
|
||||
"cssnano": "^6.0.0",
|
||||
"d3-dag": "^0.11.5",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"monaco-editor-workers": "0.36.0",
|
||||
"ol": "^7.2.2",
|
||||
"openapi-typescript-codegen": "^0.23.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
@@ -47,10 +49,11 @@
|
||||
"style-to-object": "^0.4.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-awesome": "^3.0.0",
|
||||
"svelte-awesome": "^3.2.0",
|
||||
"svelte-awesome-color-picker": "^2.4.1",
|
||||
"svelte-check": "^3.0.2",
|
||||
"svelte-highlight": "^6.2.1",
|
||||
"svelte-highlight": "^7.2.1",
|
||||
"svelte-multiselect": "^8.6.0",
|
||||
"svelte-overlay": "^1.4.1",
|
||||
"svelte-popperjs": "^1.3.2",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
@@ -67,12 +70,11 @@
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@leeoniya/ufuzzy": "^0.9.1",
|
||||
"@leeoniya/ufuzzy": "^1.0.6",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.7.6",
|
||||
"ag-grid-svelte": "^0.1.4",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chart.js": "^3.9.1",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
@@ -82,16 +84,16 @@
|
||||
"highlight.js": "^11.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-svelte": "^0.115.0",
|
||||
"monaco-editor": "0.33.0",
|
||||
"monaco-editor-workers": "0.33.0",
|
||||
"monaco-languageclient": "2.1.0",
|
||||
"monaco-yaml": "^4.0.2",
|
||||
"monaco-editor": "0.36.1",
|
||||
"monaco-languageclient": "5.0.1",
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.3.1",
|
||||
"svelte-timezone-picker": "^2.0.3",
|
||||
"tailwind-merge": "^1.9.1",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
"vscode-languageclient": "^8.1.0",
|
||||
"vscode-ws-jsonrpc": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55.1"
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
<h2 class="mt-8 mb-4">Non OAuth APIs & Resources</h2>
|
||||
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
|
||||
{#if filteredConnectsManual}
|
||||
{#each filteredConnectsManual as [key, instructions]}
|
||||
{#each filteredConnectsManual as [key, _]}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
@@ -414,7 +414,7 @@
|
||||
</div>
|
||||
{#if 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} />
|
||||
<img class="m-auto max-h-60" alt="connect" src={apiTokenApps[resource_type].img} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { truncate } from '$lib/utils'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import json from 'svelte-highlight/languages/json'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import Drawer from './common/drawer/Drawer.svelte'
|
||||
import { truncate } from '$lib/utils'
|
||||
import { DrawerContent } from './common'
|
||||
import Drawer from './common/drawer/Drawer.svelte'
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let value: any
|
||||
let jsonViewer: Drawer
|
||||
|
||||
@@ -3,32 +3,31 @@
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faDollarSign,
|
||||
faMinus,
|
||||
faPlus
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { setInputCat as computeInputCat } from '$lib/utils'
|
||||
import { Badge, Button } from './common'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { setInputCat as computeInputCat } from '$lib/utils'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import Password from './Password.svelte'
|
||||
import type VariableEditor from './VariableEditor.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { fade } from 'svelte/transition'
|
||||
import JsonEditor from './apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
import { Badge, Button } from './common'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
import NumberTypeNarrowing from './NumberTypeNarrowing.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
|
||||
import Password from './Password.svelte'
|
||||
import Range from './Range.svelte'
|
||||
import JsonEditor from './apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { X } from 'lucide-svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import type VariableEditor from './VariableEditor.svelte'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
@@ -42,7 +41,6 @@
|
||||
export let required = false
|
||||
export let pattern: undefined | string = undefined
|
||||
export let valid = required ? false : true
|
||||
export let maxRows = 10
|
||||
export let enum_: string[] | undefined = undefined
|
||||
export let disabled = false
|
||||
export let editableSchema = false
|
||||
@@ -63,8 +61,6 @@
|
||||
let seeEditable: boolean = enum_ != undefined || pattern != undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
|
||||
|
||||
$: validateInput(pattern, value)
|
||||
|
||||
let error: string = ''
|
||||
@@ -168,6 +164,7 @@
|
||||
let itemsLimit = 50
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<div class="flex flex-col w-full min-w-[250px]">
|
||||
<div>
|
||||
{#if displayHeader}
|
||||
@@ -175,6 +172,7 @@
|
||||
{/if}
|
||||
{#if editableSchema}
|
||||
<div class="p-2 my-1 text-xs border-solid border border-gray-400">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="underline"
|
||||
on:click={() => {
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="center-center min-h-screen p-4 relative bg-[#3b4252]">
|
||||
<div class="border rounded-md shadow-md bg-white w-full max-w-[640px]
|
||||
p-4 sm:py-8 sm:px-10 mb-6 md:mb-20 z-10">
|
||||
<div class="center-center min-h-screen p-4 relative bg-gray-50">
|
||||
<div
|
||||
class="border rounded-md shadow-md bg-white w-full max-w-[640px]
|
||||
p-4 sm:py-8 sm:px-10 mb-6 md:mb-20 z-10"
|
||||
>
|
||||
<div class="mb-10">
|
||||
<h1 class="text-center">
|
||||
{title}
|
||||
@@ -30,13 +32,13 @@
|
||||
</div>
|
||||
{#if !disableLogo}
|
||||
<div class="hidden lg:block absolute top-10 right-50">
|
||||
<div class="animate-[spin_100s_linear_infinite] ">
|
||||
<WindmillIcon white height="100px" width="100px" />
|
||||
<div>
|
||||
<WindmillIcon height="100px" width="100px" spin="slow" />
|
||||
</div>
|
||||
<h2 class="text-center pt-2 text-gray-100">Windmill</h2>
|
||||
<h2 class="text-center pt-2 text-gray-800">Windmill</h2>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="absolute top-0 right-0 text-2xs text-gray-300 italic px-3 py-1">
|
||||
<div class="absolute top-0 right-0 text-2xs text-gray-800 italic px-3 py-1">
|
||||
<span class="font-mono">{version}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,92 +1,328 @@
|
||||
<script context="module">
|
||||
export const OFFSET = new Date().getTimezoneOffset()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { ScheduleService } from '$lib/gen'
|
||||
import { displayDate, emptyString, formatCron, sendUserToast } from '$lib/utils'
|
||||
import CollapseLink from './CollapseLink.svelte'
|
||||
import { emptyString, formatCron } from '$lib/utils'
|
||||
import Badge from './Badge.svelte'
|
||||
|
||||
// @ts-ignore
|
||||
import Multiselect from 'svelte-multiselect'
|
||||
import TimezonePicker from 'svelte-timezone-picker'
|
||||
import CollapseLink from './CollapseLink.svelte'
|
||||
import { Button } from './common'
|
||||
|
||||
export let schedule: string
|
||||
// export let offset: number = -60 * Math.floor(new Date().getTimezoneOffset() / 60)
|
||||
export let timezone: string // = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
export let disabled = false
|
||||
export let validCRON = true
|
||||
|
||||
let preview: string[] = []
|
||||
let cronError = ''
|
||||
export let schedule: string = '0 0 12 * *'
|
||||
export let disabled = false
|
||||
let limit = 3
|
||||
// If the user has already entered a cron string, switching to the basic tab will override it.
|
||||
let executeEvery: 'second' | 'minute' | 'hour' | 'day-month' | 'month' | 'day-week' = 'minute'
|
||||
|
||||
$: !emptyString(schedule) && handleScheduleInput(schedule)
|
||||
let seconds = 30
|
||||
let minutes = 30
|
||||
let hours = 1
|
||||
const daysOfMonthOptions: number[] = Array.from(Array(31).keys()).map((i) => i + 1)
|
||||
let daysOfMonth: number[] = []
|
||||
// let lastDayOfMonth = false
|
||||
const monthsOfYearOptions: string[] = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
]
|
||||
let monthsOfYear: string[] = []
|
||||
const daysOfWeekOptions: string[] = [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday'
|
||||
]
|
||||
let daysOfWeek: string[] = []
|
||||
let UTCTime: string = ''
|
||||
|
||||
async function handleScheduleInput(input: string): Promise<void> {
|
||||
$: !emptyString(schedule) && handleScheduleInput(schedule, timezone)
|
||||
|
||||
async function handleScheduleInput(input: string, timezone: string): Promise<void> {
|
||||
try {
|
||||
preview = await ScheduleService.previewSchedule({
|
||||
requestBody: { schedule: formatCron(input), offset: OFFSET }
|
||||
requestBody: { schedule: formatCron(input), timezone }
|
||||
})
|
||||
cronError = ''
|
||||
validCRON = true
|
||||
} catch (err) {
|
||||
if (err.status == 400 && err.body.includes('cron')) {
|
||||
cronError = `Invalid cron expression`
|
||||
validCRON = false
|
||||
} else {
|
||||
validCRON = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nschedule = ''
|
||||
|
||||
$: {
|
||||
// CRON string format
|
||||
// sec min hour day of month month day of week year
|
||||
// 0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2
|
||||
|
||||
let s_daysOfMonth = ''
|
||||
// if (lastDayOfMonth) {
|
||||
// s_daysOfMonth = 'L'
|
||||
// } else
|
||||
if (daysOfMonth.length > 0) {
|
||||
s_daysOfMonth = daysOfMonth.join(',')
|
||||
} else {
|
||||
s_daysOfMonth = '*'
|
||||
}
|
||||
|
||||
let s_months = ''
|
||||
if (monthsOfYear.length > 0) {
|
||||
s_months = monthsOfYear.map((m) => m.slice(0, 3).toLowerCase()).join(',')
|
||||
} else {
|
||||
s_months = '*'
|
||||
}
|
||||
|
||||
let s_daysOfWeek = ''
|
||||
if (daysOfWeek.length > 0) {
|
||||
s_daysOfWeek = daysOfWeek.map((d) => d.slice(0, 3).toLowerCase()).join(',')
|
||||
} else {
|
||||
s_daysOfWeek = '*'
|
||||
}
|
||||
|
||||
const s_AtUTCHours = parseInt(UTCTime.split(':')[0]) || '0'
|
||||
const s_AtUTCMinutes = parseInt(UTCTime.split(':')[1]) || '0'
|
||||
|
||||
// If using the basic editor, set the cron string based on the selected options
|
||||
if (executeEvery === 'second') {
|
||||
if (seconds > 0) {
|
||||
nschedule = `*/${seconds} * * * *`
|
||||
} else {
|
||||
nschedule = `* * * * *`
|
||||
}
|
||||
} else if (executeEvery === 'minute') {
|
||||
if (minutes > 0) {
|
||||
nschedule = `0 */${minutes} * * *`
|
||||
} else {
|
||||
nschedule = `* * * * *`
|
||||
}
|
||||
} else if (executeEvery === 'hour') {
|
||||
if (hours > 0) {
|
||||
nschedule = `0 0 */${hours} * *`
|
||||
} else {
|
||||
nschedule = `* * * * *`
|
||||
}
|
||||
} else if (executeEvery === 'day-month') {
|
||||
nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} ${s_daysOfMonth} *`
|
||||
} else if (executeEvery === 'month') {
|
||||
nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} ${s_daysOfMonth} ${s_months}`
|
||||
} else if (executeEvery === 'day-week') {
|
||||
nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} * * ${s_daysOfWeek}`
|
||||
}
|
||||
}
|
||||
|
||||
$: dateFormatter = new Intl.DateTimeFormat('en-GB', {
|
||||
weekday: 'short',
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZone: timezone,
|
||||
timeZoneName: 'short'
|
||||
}).format
|
||||
</script>
|
||||
|
||||
<div class="max-w-xl">
|
||||
<div class="w-full text-right text-red-600 text-2xs grow">{cronError}</div>
|
||||
<div class="flex flex-row items-end max-w-5xl">
|
||||
<label class="text-xs min-w-max mr-2 self-center" for="cron-schedule">CRON expression</label>
|
||||
<input
|
||||
class="inline-block"
|
||||
type="text"
|
||||
id="cron-schedule"
|
||||
name="cron-schedule"
|
||||
bind:value={schedule}
|
||||
{disabled}
|
||||
/>
|
||||
</div>
|
||||
{#if !disabled}
|
||||
<div class="flex flex-row text-xs text-blue-500 gap-3 pl-28 mb-2">
|
||||
<button
|
||||
on:click={() => {
|
||||
schedule = '0 */15 * * *'
|
||||
cronError = ''
|
||||
}}>every 15 min</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
schedule = '0 0 * * * *'
|
||||
cronError = ''
|
||||
}}>every hour</button
|
||||
>
|
||||
<button
|
||||
on:click={() => {
|
||||
schedule = '0 0 8 * * *'
|
||||
cronError = ''
|
||||
}}>once a day at 8AM</button
|
||||
>
|
||||
<div class="w-full flex space-x-16 p-4">
|
||||
<div class="w-full flex flex-col space-y-2">
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<small class="font-bold">Cron</small>
|
||||
<input
|
||||
class="inline-block"
|
||||
type="text"
|
||||
id="cron-schedule"
|
||||
name="cron-schedule"
|
||||
placeholder="*/30 * * * *"
|
||||
bind:value={schedule}
|
||||
{disabled}
|
||||
/>
|
||||
{#if !validCRON}
|
||||
<small class="text-red-600"> Invalid cron syntax </small>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<CollapseLink text="preview next runs" open={true}>
|
||||
{#if preview && preview.length > 0}
|
||||
<div class="text-sm text-gray-700 border p-2 rounded-md">
|
||||
<div class="flex flex-row justify-between">The next runs will be scheduled at:</div>
|
||||
<ul class="list-disc mx-12">
|
||||
{#each preview.slice(0, limit) as p}
|
||||
<li class="mx-2 text-gray-700 text-sm">{displayDate(p, true)}</li>
|
||||
{/each}
|
||||
<li class="text-sm mx-2">...</li>
|
||||
{#if limit != 10}
|
||||
<button class="underline text-gray-400" on:click={() => (limit = 10)}>Load more</button>
|
||||
{:else}
|
||||
<button class="underline text-gray-400" on:click={() => (limit = 3)}>Load less</button>
|
||||
{/if}
|
||||
</ul>
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<small class="font-bold">Timezone</small>
|
||||
|
||||
{#if disabled}
|
||||
<div>
|
||||
<Badge>{timezone}</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<TimezonePicker {timezone} on:update={(e) => (timezone = e.detail.timezone)} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !disabled}
|
||||
<div class="w-full">
|
||||
<CollapseLink text="Use simplified builder">
|
||||
<div class="w-full flex flex-col gap-4 mt-4">
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<small class="font-bold">Execute schedule every</small>
|
||||
|
||||
<div class="w-full flex gap-4">
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<select
|
||||
{disabled}
|
||||
name="execute_every"
|
||||
id="execute_every"
|
||||
bind:value={executeEvery}
|
||||
>
|
||||
<option value="second">Second(s)</option>
|
||||
<option value="minute">Minute(s)</option>
|
||||
<option value="hour">Hour(s)</option>
|
||||
<option value="day-month">Day of the month</option>
|
||||
<option value="month">Month(s)</option>
|
||||
<option value="day-week">Day of the week</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-1 justify-center">
|
||||
{#if executeEvery == 'second'}
|
||||
<input {disabled} type="number" min="0" max="59" bind:value={seconds} />
|
||||
<small>Valid range 0-59</small>
|
||||
{:else if executeEvery == 'minute'}
|
||||
<input {disabled} type="number" min="0" max="59" bind:value={minutes} />
|
||||
<small>Valid range 0-59</small>
|
||||
{:else if executeEvery == 'hour'}
|
||||
<input {disabled} type="number" min="0" max="23" bind:value={hours} />
|
||||
<small>Valid range 0-23</small>
|
||||
{:else if executeEvery == 'day-month'}
|
||||
<!-- <div class="w-full flex">
|
||||
<label for="lastDayOfMonth" class="w-full flex items-center gap-2">
|
||||
<div class="flex">
|
||||
<input type="checkbox" id="lastDayOfMonth" bind:checked={lastDayOfMonth} />
|
||||
</div>
|
||||
<small> Last day of the month </small>
|
||||
</label>
|
||||
</div> -->
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
{#if executeEvery == 'month'}
|
||||
<div class="w-full flex flex-col">
|
||||
<Multiselect
|
||||
{disabled}
|
||||
bind:selected={monthsOfYear}
|
||||
options={monthsOfYearOptions}
|
||||
selectedOptionsDraggable={false}
|
||||
placeholder="Every month"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if executeEvery == 'day-week'}
|
||||
<div class="w-full flex flex-col">
|
||||
<Multiselect
|
||||
{disabled}
|
||||
bind:selected={daysOfWeek}
|
||||
options={daysOfWeekOptions}
|
||||
selectedOptionsDraggable={false}
|
||||
placeholder="Every day"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if executeEvery == 'day-month' || executeEvery == 'month'}
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
{#if executeEvery == 'month'}
|
||||
<small class="font-bold">On day of the month</small>
|
||||
{/if}
|
||||
<div class="w-full flex gap-4">
|
||||
<div class="w-full flex">
|
||||
<Multiselect
|
||||
{disabled}
|
||||
bind:selected={daysOfMonth}
|
||||
options={daysOfMonthOptions}
|
||||
selectedOptionsDraggable={false}
|
||||
placeholder="Every day"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- {#if executeEvery == 'month'}
|
||||
<div class="w-full flex">
|
||||
<label for="lastDayOfMonth" class="w-full flex items-center gap-2">
|
||||
<div class="flex">
|
||||
<input type="checkbox" id="lastDayOfMonth" bind:checked={lastDayOfMonth} />
|
||||
</div>
|
||||
<small> Last day of the month </small>
|
||||
</label>
|
||||
</div>
|
||||
{/if} -->
|
||||
</div>
|
||||
<small>Schedule will only execute on valid calendar days</small>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if executeEvery == 'day-month' || executeEvery == 'month' || executeEvery == 'day-week'}
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<small class="font-bold">At UTC Time</small>
|
||||
<input
|
||||
{disabled}
|
||||
type="time"
|
||||
name="atUTCTime"
|
||||
id="atUTCTime"
|
||||
bind:value={UTCTime}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col gap-1">
|
||||
<small class="font-bold">Preview New Cron</small>
|
||||
|
||||
<div class="flex p-2 px-4 rounded-md bg-gray-100">
|
||||
<span>{nschedule}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<Button variant="border" color="dark" size="xs" on:click={() => (schedule = nschedule)}
|
||||
>Set Schedule</Button
|
||||
>
|
||||
</div>
|
||||
</CollapseLink>
|
||||
</div>
|
||||
{/if}
|
||||
</CollapseLink>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex flex-col space-y-2">
|
||||
<h3>Execution summary</h3>
|
||||
<hr />
|
||||
<div class="flex flex-col space-y-2">
|
||||
<small>Estimated upcoming events ({timezone})</small>
|
||||
<div class="flex flex-col rounded-md p-4 border text-gray-600">
|
||||
{#each preview as date}
|
||||
<div class="flex items-center space-x-2">
|
||||
<span>{dateFormatter(new Date(date))}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -99,19 +99,17 @@
|
||||
<div class="mb-2 text-gray-500 text-sm bg-gray-50/20">
|
||||
as JSON <input class="windmillapp" type="checkbox" bind:checked={forceJson} /></div
|
||||
>{/if}{#if typeof result == 'object' && Object.keys(result).length > 0}<div
|
||||
class="mb-2 min-w-[360px] text-sm text-gray-700 relative"
|
||||
class="mb-2 w-full text-sm text-gray-700 relative"
|
||||
>The result keys are: <b>{truncate(Object.keys(result).join(', '), 50)}</b>
|
||||
<div class="text-gray-500 text-sm absolute top-0 right-2">
|
||||
<div class="text-gray-500 text-sm absolute top-6 right-0">
|
||||
<button on:click={jsonViewer.openDrawer}>Expand JSON</button>
|
||||
</div></div
|
||||
>{/if}{#if !forceJson && resultKind == 'table-col'}<div
|
||||
class="grid grid-flow-col-dense border border-gray-200 rounded-md "
|
||||
class="grid grid-flow-col-dense border border-gray-200 rounded-md"
|
||||
>
|
||||
{#each Object.keys(result) as col}
|
||||
<div class="flex flex-col max-h-40 min-w-full">
|
||||
<div
|
||||
class="px-12 text-left uppercase border-b bg-gray-50 overflow-hidden rounded-t-md "
|
||||
>
|
||||
<div class="px-12 text-left uppercase border-b bg-gray-50 overflow-hidden rounded-t-md">
|
||||
{col}
|
||||
</div>
|
||||
{#if Array.isArray(result[col])}
|
||||
@@ -125,7 +123,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'table-row'}<div
|
||||
class="grid grid-flow-col-dense border border-gray-200 "
|
||||
class="grid grid-flow-col-dense border border-gray-200"
|
||||
>
|
||||
<TableCustom>
|
||||
<tbody slot="body">
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
dispatch('click', { item: item?.eventName })
|
||||
}
|
||||
}}
|
||||
class="block w-full whitespace-nowrap hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30
|
||||
px-4 py-2 text-sm text-gray-700 text-left
|
||||
class="block w-full whitespace-nowrap hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30
|
||||
px-4 py-2 text-sm text-gray-700 text-left
|
||||
{item.disabled ? 'bg-gray-200' : ''}
|
||||
{item.separatorTop ? 'border-t' : ''} {item.separatorBottom ? 'border-b' : ''} {item.type ==
|
||||
'delete'
|
||||
@@ -72,7 +72,7 @@
|
||||
<a
|
||||
href={item.href}
|
||||
on:click|stopPropagation|preventDefault={() => goto(item.href ?? '')}
|
||||
class="block w-full px-4 font-semibold text-left py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30
|
||||
class="block w-full px-4 font-semibold text-left py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30
|
||||
{item.disabled ? 'bg-gray-200' : ''}"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
@@ -89,9 +89,10 @@
|
||||
{item.displayName}
|
||||
</a>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class:bg-gray-50={item.disabled}
|
||||
class="block text-left px-4 py-2 text-sm text-gray-700 cursor-auto"
|
||||
class="block text-left px-4 py-2 text-sm text-gray-700 cursor-auto"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
id="user-menu-item-{name}-{i}}"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts" context="module">
|
||||
import getMessageServiceOverride from 'vscode/service-override/messages'
|
||||
import getDialogServiceOverride from 'vscode/service-override/dialogs'
|
||||
import getNotificationServiceOverride from 'vscode/service-override/notifications'
|
||||
import { StandaloneServices } from 'vscode/services'
|
||||
|
||||
try {
|
||||
StandaloneServices?.initialize({
|
||||
...getMessageServiceOverride(document.body)
|
||||
...getNotificationServiceOverride(document.body),
|
||||
...getDialogServiceOverride()
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -12,35 +14,34 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { browser, dev } from '$app/environment'
|
||||
import { browser } from '$app/environment'
|
||||
import { page } from '$app/stores'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
|
||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
||||
|
||||
import { buildWorkerDefinition } from 'monaco-editor-workers'
|
||||
import type {
|
||||
Disposable,
|
||||
DocumentUri,
|
||||
MessageTransports,
|
||||
MonacoLanguageClient
|
||||
} from 'monaco-languageclient'
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { languages, editor as meditor, KeyCode, KeyMod, Uri as mUri } from 'monaco-editor'
|
||||
|
||||
languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
target: languages.typescript.ScriptTarget.Latest,
|
||||
allowNonTsExtensions: true,
|
||||
noLib: true
|
||||
import 'monaco-editor/esm/vs/editor/edcore.main'
|
||||
import {
|
||||
editor as meditor,
|
||||
KeyCode,
|
||||
KeyMod,
|
||||
Uri as mUri,
|
||||
languages
|
||||
} from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/go/go.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution'
|
||||
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'
|
||||
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
|
||||
import { MonacoLanguageClient, MonacoServices } from 'monaco-languageclient'
|
||||
import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'
|
||||
import { CloseAction, ErrorAction, RequestType } from 'vscode-languageclient'
|
||||
import * as vscode from 'vscode'
|
||||
languages.typescript.typescriptDefaults.setModeConfiguration({
|
||||
completionItems: false,
|
||||
definitions: false,
|
||||
hovers: false
|
||||
})
|
||||
languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: true,
|
||||
noSuggestionDiagnostics: true,
|
||||
noSyntaxValidation: true
|
||||
})
|
||||
languages.typescript.typescriptDefaults.setExtraLibs([])
|
||||
|
||||
meditor.defineTheme('myTheme', {
|
||||
base: 'vs',
|
||||
@@ -53,32 +54,45 @@
|
||||
})
|
||||
meditor.setTheme('myTheme')
|
||||
|
||||
import {
|
||||
BASH_INIT_CODE,
|
||||
DENO_INIT_CODE_CLEAR,
|
||||
GO_INIT_CODE,
|
||||
PYTHON_INIT_CODE_CLEAR
|
||||
} from '$lib/script_helpers'
|
||||
import {
|
||||
createHash as randomHash,
|
||||
editorConfig,
|
||||
langToExt,
|
||||
updateOptions
|
||||
} from '$lib/editorUtils'
|
||||
import {
|
||||
BASH_INIT_CODE,
|
||||
DENO_INIT_CODE_CLEAR,
|
||||
GO_INIT_CODE,
|
||||
PYTHON_INIT_CODE_CLEAR
|
||||
} from '$lib/script_helpers'
|
||||
import type { Disposable } from 'vscode'
|
||||
import type { DocumentUri, MessageTransports } from 'vscode-languageclient'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { buildWorkerDefinition } from './build_workers'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { UserService } from '$lib/gen'
|
||||
|
||||
let divEl: HTMLDivElement | null = null
|
||||
let editor: meditor.IStandaloneCodeEditor
|
||||
|
||||
export let lang: 'typescript' | 'python' | 'go' | 'shell'
|
||||
export let code: string = ''
|
||||
export let hash: string = randomHash()
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined
|
||||
export let formatAction: (() => void) | undefined = undefined
|
||||
export let automaticLayout = true
|
||||
export let websocketAlive = { pyright: false, black: false, deno: false, go: false }
|
||||
export let shouldBindKey: boolean = true
|
||||
export let fixedOverflowWidgets = true
|
||||
export let path: string = randomHash()
|
||||
|
||||
if (path == '' || path == undefined || path.startsWith('/')) {
|
||||
path = randomHash()
|
||||
}
|
||||
|
||||
let initialPath: string = path
|
||||
|
||||
$: path != initialPath && handlePathChange()
|
||||
|
||||
let websockets: [MonacoLanguageClient, WebSocket][] = []
|
||||
let websocketInterval: NodeJS.Timer | undefined
|
||||
@@ -87,28 +101,12 @@
|
||||
let disposeMethod: () => void | undefined
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const uri = `file:///tmp/monaco/${hash}.${langToExt(lang)}`
|
||||
const uri =
|
||||
lang == 'go' ? `file:///tmp/monaco/${randomHash()}.go` : `file:///${path}.${langToExt(lang)}`
|
||||
|
||||
if (browser) {
|
||||
if (dev) {
|
||||
buildWorkerDefinition(
|
||||
'../../../node_modules/monaco-editor-workers/dist/workers',
|
||||
import.meta.url,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
self.MonacoEnvironment = {
|
||||
getWorker: function (_moduleId: any, label: string) {
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker()
|
||||
} else {
|
||||
return new editorWorker()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (lang != 'typescript') {
|
||||
buildWorkerDefinition('../../../workers', import.meta.url, false)
|
||||
// }
|
||||
|
||||
export function getCode(): string {
|
||||
return editor?.getValue() ?? ''
|
||||
@@ -136,27 +134,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function setCode(ncode: string): void {
|
||||
export function setCode(ncode: string, noHistory: boolean = false): void {
|
||||
code = ncode
|
||||
if (editor?.getModel()) {
|
||||
// editor.setValue(ncode)
|
||||
editor.pushUndoStop()
|
||||
if (noHistory) {
|
||||
editor?.setValue(ncode)
|
||||
} else {
|
||||
if (editor?.getModel()) {
|
||||
// editor.setValue(ncode)
|
||||
editor.pushUndoStop()
|
||||
|
||||
editor.executeEdits('set', [
|
||||
{
|
||||
range: editor.getModel()!.getFullModelRange(), // full range
|
||||
text: ncode
|
||||
}
|
||||
])
|
||||
editor.executeEdits('set', [
|
||||
{
|
||||
range: editor.getModel()!.getFullModelRange(), // full range
|
||||
text: ncode
|
||||
}
|
||||
])
|
||||
|
||||
editor.pushUndoStop()
|
||||
editor.pushUndoStop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function format() {
|
||||
if (editor) {
|
||||
code = getCode()
|
||||
editor.getAction('editor.action.formatDocument').run()
|
||||
editor?.getAction('editor.action.formatDocument')?.run()
|
||||
if (formatAction) {
|
||||
formatAction()
|
||||
}
|
||||
@@ -182,22 +184,14 @@
|
||||
|
||||
export async function reloadWebsocket() {
|
||||
await closeWebsockets()
|
||||
const { MonacoLanguageClient } = await import('monaco-languageclient')
|
||||
const { CloseAction, ErrorAction } = await import('vscode-languageclient')
|
||||
const { toSocket, WebSocketMessageReader, WebSocketMessageWriter } = await import(
|
||||
'vscode-ws-jsonrpc'
|
||||
)
|
||||
const vscode = await import('vscode')
|
||||
const { RequestType } = await import('vscode-jsonrpc')
|
||||
// install Monaco language client services
|
||||
const { MonacoServices } = await import('monaco-languageclient')
|
||||
|
||||
monacoServices = MonacoServices.install()
|
||||
|
||||
function createLanguageClient(
|
||||
transports: MessageTransports,
|
||||
name: string,
|
||||
initializationOptions?: any
|
||||
initializationOptions: any,
|
||||
middlewareOptions: ((params, token, next) => any) | undefined
|
||||
) {
|
||||
const client = new MonacoLanguageClient({
|
||||
name: name,
|
||||
@@ -216,13 +210,11 @@
|
||||
initializationOptions,
|
||||
middleware: {
|
||||
workspace: {
|
||||
configuration: (params, token, configuration) => {
|
||||
return [
|
||||
{
|
||||
enable: true
|
||||
}
|
||||
]
|
||||
}
|
||||
configuration:
|
||||
middlewareOptions ??
|
||||
((params, token, next) => {
|
||||
return [{ enabled: true }]
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -235,7 +227,12 @@
|
||||
return client
|
||||
}
|
||||
|
||||
async function connectToLanguageServer(url: string, name: string, options?: any) {
|
||||
async function connectToLanguageServer(
|
||||
url: string,
|
||||
name: string,
|
||||
initOptions: any,
|
||||
middlewareOptions: any
|
||||
) {
|
||||
try {
|
||||
const webSocket = new WebSocket(url)
|
||||
|
||||
@@ -243,7 +240,15 @@
|
||||
const socket = toSocket(webSocket)
|
||||
const reader = new WebSocketMessageReader(socket)
|
||||
const writer = new WebSocketMessageWriter(socket)
|
||||
const languageClient = createLanguageClient({ reader, writer }, name, options)
|
||||
const languageClient = createLanguageClient(
|
||||
{ reader, writer },
|
||||
name,
|
||||
initOptions,
|
||||
middlewareOptions
|
||||
)
|
||||
// if (middlewareOptions != undefined) {
|
||||
// languageClient.registerNotUsedFeatures()
|
||||
// }
|
||||
websockets.push([languageClient, webSocket])
|
||||
|
||||
// HACK ALERT: for some reasons, the client need to be restarted to take into account the 'go get <dep>' command
|
||||
@@ -273,12 +278,13 @@
|
||||
})
|
||||
|
||||
try {
|
||||
console.log('started client')
|
||||
console.log('starting client')
|
||||
await languageClient.start()
|
||||
console.log('started client')
|
||||
} catch (err) {
|
||||
console.log('err at client')
|
||||
console.error(err)
|
||||
throw new Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
lastWsAttempt = new Date()
|
||||
@@ -309,64 +315,138 @@
|
||||
}
|
||||
|
||||
const wsProtocol = $page.url.protocol == 'https:' ? 'wss' : 'ws'
|
||||
|
||||
let encodedImportMap = ''
|
||||
if (lang == 'typescript') {
|
||||
await connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/deno`, 'deno', {
|
||||
certificateStores: null,
|
||||
enablePaths: [],
|
||||
config: null,
|
||||
importMap: null,
|
||||
internalDebug: false,
|
||||
lint: false,
|
||||
path: null,
|
||||
tlsCertificate: null,
|
||||
unsafelyIgnoreCertificateErrors: null,
|
||||
unstable: true,
|
||||
enable: true,
|
||||
cache: null,
|
||||
codeLens: {
|
||||
implementations: true,
|
||||
references: true
|
||||
},
|
||||
suggest: {
|
||||
autoImports: true,
|
||||
completeFunctionCalls: false,
|
||||
names: true,
|
||||
paths: true,
|
||||
if (path && path.split('/').length > 2) {
|
||||
let expiration = new Date()
|
||||
expiration.setHours(expiration.getHours() + 2)
|
||||
const token = await UserService.createToken({
|
||||
requestBody: { label: 'Ephemeral lsp token', expiration: expiration.toISOString() }
|
||||
})
|
||||
let root =
|
||||
$page.url.protocol +
|
||||
'//' +
|
||||
$page.url.host +
|
||||
'/api/scripts_u/tokened_raw/' +
|
||||
$workspaceStore +
|
||||
'/' +
|
||||
token
|
||||
const importMap = {
|
||||
imports: {
|
||||
autoDiscover: true,
|
||||
hosts: {
|
||||
'https://deno.land': true
|
||||
'file:///': root + '/'
|
||||
}
|
||||
}
|
||||
let path_splitted = path.split('/')
|
||||
for (let c = 0; c < path_splitted.length; c++) {
|
||||
let key = 'file://./'
|
||||
for (let i = 0; i < c; i++) {
|
||||
key += '../'
|
||||
}
|
||||
let url = path_splitted.slice(0, -c - 1).join('/')
|
||||
let ending = c == path_splitted.length - 1 ? '' : '/'
|
||||
importMap['imports'][key] = `${root}/${url}${ending}`
|
||||
}
|
||||
encodedImportMap = 'data:text/plain;base64,' + btoa(JSON.stringify(importMap))
|
||||
}
|
||||
await connectToLanguageServer(
|
||||
`${wsProtocol}://${$page.url.host}/ws/deno`,
|
||||
'deno',
|
||||
{
|
||||
certificateStores: null,
|
||||
enablePaths: [],
|
||||
config: null,
|
||||
importMap: encodedImportMap,
|
||||
internalDebug: false,
|
||||
lint: false,
|
||||
path: null,
|
||||
tlsCertificate: null,
|
||||
unsafelyIgnoreCertificateErrors: null,
|
||||
unstable: true,
|
||||
enable: true,
|
||||
codeLens: {
|
||||
implementations: true,
|
||||
references: true,
|
||||
referencesAllFunction: false
|
||||
},
|
||||
suggest: {
|
||||
autoImports: true,
|
||||
completeFunctionCalls: false,
|
||||
names: true,
|
||||
paths: true,
|
||||
imports: {
|
||||
autoDiscover: true,
|
||||
hosts: {
|
||||
'https://deno.land': true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {
|
||||
return [
|
||||
{
|
||||
enable: true
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
)
|
||||
} else if (lang === 'python') {
|
||||
await connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/pyright`, 'pyright', {
|
||||
executionEnvironments: [
|
||||
{
|
||||
root: '/tmp/pyright',
|
||||
pythonVersion: '3.7',
|
||||
pythonPlatform: 'platform',
|
||||
extraPaths: []
|
||||
await connectToLanguageServer(
|
||||
`${wsProtocol}://${$page.url.host}/ws/pyright`,
|
||||
'pyright',
|
||||
{},
|
||||
(params, token, next) => {
|
||||
if (params.items.find((x) => x.section === 'python')) {
|
||||
return [
|
||||
{
|
||||
analysis: {
|
||||
useLibraryCodeForTypes: true,
|
||||
autoImportCompletions: true,
|
||||
diagnosticSeverityOverrides: { reportMissingImports: 'none' },
|
||||
typeCheckingMode: 'basic'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
if (params.items.find((x) => x.section === 'python.analysis')) {
|
||||
return [
|
||||
{
|
||||
useLibraryCodeForTypes: true,
|
||||
autoImportCompletions: true,
|
||||
diagnosticSeverityOverrides: { reportMissingImports: 'none' },
|
||||
typeCheckingMode: 'basic'
|
||||
}
|
||||
]
|
||||
}
|
||||
return next(params, token)
|
||||
}
|
||||
)
|
||||
|
||||
connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/black`, 'black', {
|
||||
formatters: {
|
||||
black: {
|
||||
command: 'black',
|
||||
args: ['--quiet', '-']
|
||||
connectToLanguageServer(
|
||||
`${wsProtocol}://${$page.url.host}/ws/black`,
|
||||
'black',
|
||||
{
|
||||
formatters: {
|
||||
black: {
|
||||
command: 'black',
|
||||
args: ['--quiet', '-']
|
||||
}
|
||||
},
|
||||
formatFiletypes: {
|
||||
python: 'black'
|
||||
}
|
||||
},
|
||||
formatFiletypes: {
|
||||
python: 'black'
|
||||
}
|
||||
})
|
||||
undefined
|
||||
)
|
||||
} else if (lang === 'go') {
|
||||
connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/go`, 'go', {
|
||||
'build.allowImplicitNetworkAccess': true
|
||||
})
|
||||
connectToLanguageServer(
|
||||
`${wsProtocol}://${$page.url.host}/ws/go`,
|
||||
'go',
|
||||
{
|
||||
'build.allowImplicitNetworkAccess': true
|
||||
},
|
||||
undefined
|
||||
)
|
||||
}
|
||||
|
||||
websocketInterval && clearInterval(websocketInterval)
|
||||
@@ -397,6 +477,13 @@
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
let pathTimeout: NodeJS.Timeout | undefined = undefined
|
||||
function handlePathChange() {
|
||||
initialPath = path
|
||||
pathTimeout && clearTimeout(pathTimeout)
|
||||
pathTimeout = setTimeout(reloadWebsocket, 3000)
|
||||
}
|
||||
|
||||
async function closeWebsockets() {
|
||||
command && command.dispose()
|
||||
command = undefined
|
||||
@@ -417,12 +504,14 @@
|
||||
websocketInterval && clearInterval(websocketInterval)
|
||||
}
|
||||
|
||||
let widgets: HTMLElement | undefined = document.getElementById('monaco-widgets-root') ?? undefined
|
||||
async function loadMonaco() {
|
||||
const model = meditor.createModel(code, lang, mUri.parse(uri))
|
||||
|
||||
model.updateOptions(updateOptions)
|
||||
editor = meditor.create(divEl as HTMLDivElement, {
|
||||
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
overflowWidgetsDomNode: widgets,
|
||||
tabSize: lang == 'python' ? 4 : 2
|
||||
})
|
||||
|
||||
@@ -506,7 +595,7 @@
|
||||
|
||||
<div bind:this={divEl} class="{$$props.class} editor" />
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.editor {
|
||||
@apply p-0 border rounded-md border-gray-50;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
import Kbd from './common/kbd/Kbd.svelte'
|
||||
|
||||
export let lang: 'python3' | 'deno' | 'go' | 'bash'
|
||||
export let editor: Editor
|
||||
export let editor: Editor | undefined
|
||||
export let websocketAlive: { pyright: boolean; black: boolean; deno: boolean; go: boolean }
|
||||
export let iconOnly: boolean = false
|
||||
export let validCode: boolean = true
|
||||
@@ -50,10 +50,10 @@
|
||||
undefined
|
||||
|
||||
function addEditorActions() {
|
||||
editor.addAction('insert-variable', 'Windmill: Insert variable', () => {
|
||||
editor?.addAction('insert-variable', 'Windmill: Insert variable', () => {
|
||||
variablePicker.openDrawer()
|
||||
})
|
||||
editor.addAction('insert-resource', 'Windmill: Insert resource', () => {
|
||||
editor?.addAction('insert-resource', 'Windmill: Insert resource', () => {
|
||||
resourcePicker.openDrawer()
|
||||
})
|
||||
}
|
||||
@@ -79,6 +79,8 @@
|
||||
codeViewer?.openDrawer?.()
|
||||
codeObj = await getScriptByPath(e.detail.path ?? '')
|
||||
}
|
||||
|
||||
let version = __pkg__.version
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={scriptPicker} size="900px">
|
||||
@@ -108,6 +110,7 @@
|
||||
<ItemPicker
|
||||
bind:this={contextualVariablePicker}
|
||||
pickCallback={(path, name) => {
|
||||
if (!editor) return
|
||||
if (lang == 'deno') {
|
||||
editor.insertAtCursor(`Deno.env.get('${name}')`)
|
||||
} else if (lang == 'python3') {
|
||||
@@ -133,10 +136,11 @@
|
||||
<ItemPicker
|
||||
bind:this={variablePicker}
|
||||
pickCallback={(path, name) => {
|
||||
if (!editor) return
|
||||
if (lang == 'deno') {
|
||||
if (!editor.getCode().includes('import * as wmill from')) {
|
||||
editor.insertAtBeginning(
|
||||
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'\n`
|
||||
`import * as wmill from 'https://deno.land/x/windmill@v${version}/mod.ts'\n`
|
||||
)
|
||||
}
|
||||
editor.insertAtCursor(`(await wmill.getVariable('${path}'))`)
|
||||
@@ -180,10 +184,11 @@
|
||||
<ItemPicker
|
||||
bind:this={resourcePicker}
|
||||
pickCallback={(path, _) => {
|
||||
if (!editor) return
|
||||
if (lang == 'deno') {
|
||||
if (!editor.getCode().includes('import * as wmill from')) {
|
||||
editor.insertAtBeginning(
|
||||
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'\n`
|
||||
`import * as wmill from 'https://deno.land/x/windmill@v${version}/mod.ts'\n`
|
||||
)
|
||||
}
|
||||
editor.insertAtCursor(`(await wmill.getResource('${path}'))`)
|
||||
@@ -233,7 +238,13 @@
|
||||
{validCode ? 'Valid' : 'Invalid'}
|
||||
</Badge>
|
||||
<div class="flex items-center divide-x">
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="pr-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="pr-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
@@ -245,11 +256,15 @@
|
||||
>
|
||||
+Context Var
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add context variable
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add context variable</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
@@ -261,11 +276,15 @@
|
||||
>
|
||||
+Variable
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add variable
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add variable</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -277,33 +296,41 @@
|
||||
>
|
||||
+Resource
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add resource
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add resource</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.clearContent}
|
||||
on:click={editor?.clearContent}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faRotateLeft }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reset
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Reset</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.reloadWebsocket}
|
||||
on:click={editor?.reloadWebsocket}
|
||||
startIcon={{ icon: faRotate }}
|
||||
>
|
||||
{#if !iconOnly}
|
||||
@@ -320,17 +347,21 @@
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reload assistant
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Reload assistant</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.format}
|
||||
on:click={editor?.format}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faBroom }}
|
||||
>
|
||||
@@ -342,7 +373,13 @@
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
@@ -352,15 +389,13 @@
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Script
|
||||
Explore other scripts
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Script
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Script</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
span.green {
|
||||
@apply text-green-600 animate-[pulse_5s_ease-in-out_infinite];
|
||||
}
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
import { initHistory, redo, undo } from '$lib/history'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { encodeState, formatCron, loadHubScripts, sendUserToast } from '$lib/utils'
|
||||
import { faCalendarAlt, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCalendarAlt, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { setContext } from 'svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import { Button, ButtonPopup, ButtonPopupItem, UndoRedo } from './common'
|
||||
import { Badge, Button, UndoRedo } from './common'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { OFFSET } from './CronInput.svelte'
|
||||
import ScriptEditorDrawer from './flows/content/ScriptEditorDrawer.svelte'
|
||||
import FlowEditor from './flows/FlowEditor.svelte'
|
||||
import ScriptEditorDrawer from './flows/content/ScriptEditorDrawer.svelte'
|
||||
import type { FlowState } from './flows/flowState'
|
||||
import { dfs } from './flows/flowStore'
|
||||
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
|
||||
@@ -20,6 +19,7 @@
|
||||
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import { cleanInputs } from './flows/utils'
|
||||
import { Pen } from 'lucide-svelte'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let selectedId: string | undefined
|
||||
@@ -29,7 +29,7 @@
|
||||
export let flowStateStore: Writable<FlowState>
|
||||
|
||||
async function createSchedule(path: string) {
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
const { cron, timezone, args, enabled } = $scheduleStore
|
||||
|
||||
try {
|
||||
await ScheduleService.createSchedule({
|
||||
@@ -37,7 +37,7 @@
|
||||
requestBody: {
|
||||
path: path,
|
||||
schedule: formatCron(cron),
|
||||
offset: OFFSET,
|
||||
timezone,
|
||||
script_path: path,
|
||||
is_flow: true,
|
||||
args,
|
||||
@@ -53,73 +53,79 @@
|
||||
|
||||
async function saveFlow(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
const flow = cleanInputs($flowStore)
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
$dirtyStore = false
|
||||
if (initialPath === '') {
|
||||
localStorage.removeItem('flow')
|
||||
await FlowService.createFlow({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
try {
|
||||
const flow = cleanInputs($flowStore)
|
||||
const { cron, timezone, args, enabled } = $scheduleStore
|
||||
$dirtyStore = false
|
||||
if (initialPath === '') {
|
||||
localStorage.removeItem('flow')
|
||||
await FlowService.createFlow({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
})
|
||||
if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
})
|
||||
if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem(`flow-${initialPath}`)
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
})
|
||||
const scheduleExists = await ScheduleService.existsSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path
|
||||
})
|
||||
if (scheduleExists) {
|
||||
const schedule = await ScheduleService.getSchedule({
|
||||
} else {
|
||||
localStorage.removeItem(`flow-${initialPath}`)
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
})
|
||||
const scheduleExists = await ScheduleService.existsSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path
|
||||
})
|
||||
if (JSON.stringify(schedule.args) != JSON.stringify(args) || schedule.schedule != cron) {
|
||||
await ScheduleService.updateSchedule({
|
||||
if (scheduleExists) {
|
||||
const schedule = await ScheduleService.getSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: {
|
||||
schedule: formatCron(cron),
|
||||
args
|
||||
}
|
||||
path: flow.path
|
||||
})
|
||||
if (JSON.stringify(schedule.args) != JSON.stringify(args) || schedule.schedule != cron) {
|
||||
await ScheduleService.updateSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: {
|
||||
schedule: formatCron(cron),
|
||||
timezone,
|
||||
args
|
||||
}
|
||||
})
|
||||
}
|
||||
if (enabled != schedule.enabled) {
|
||||
await ScheduleService.setScheduleEnabled({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: { enabled }
|
||||
})
|
||||
}
|
||||
} else if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
if (enabled != schedule.enabled) {
|
||||
await ScheduleService.setScheduleEnabled({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: { enabled }
|
||||
})
|
||||
}
|
||||
} else if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
}
|
||||
loadingSave = false
|
||||
if (leave) {
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
} else if (initialPath !== $flowStore.path) {
|
||||
initialPath = $flowStore.path
|
||||
goto(`/flows/edit/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
loadingSave = false
|
||||
if (leave) {
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
} else if (initialPath !== $flowStore.path) {
|
||||
initialPath = $flowStore.path
|
||||
goto(`/flows/edit/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(`The flow could not be saved: ${err.body}`, true)
|
||||
loadingSave = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +156,12 @@
|
||||
|
||||
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
|
||||
|
||||
const scheduleStore = writable<Schedule>({ args: {}, cron: '', enabled: false })
|
||||
const scheduleStore = writable<Schedule>({
|
||||
args: {},
|
||||
cron: '',
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
enabled: false
|
||||
})
|
||||
const previewArgsStore = writable<Record<string, any>>(initialArgs)
|
||||
const scriptEditorDrawer = writable<ScriptEditorDrawer | undefined>(undefined)
|
||||
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
|
||||
@@ -182,6 +193,7 @@
|
||||
.catch(() => {
|
||||
scheduleStore.set({
|
||||
cron: '0 */5 * * *',
|
||||
timezone: 'UTC',
|
||||
args: {},
|
||||
enabled: false
|
||||
})
|
||||
@@ -251,6 +263,23 @@
|
||||
...dfs($flowStore.value.modules, (module) => module.id)
|
||||
]
|
||||
}
|
||||
|
||||
const dropdownItems: Array<{
|
||||
label: string
|
||||
onClick: () => void
|
||||
}> = [
|
||||
{
|
||||
label: 'Save and exit',
|
||||
onClick: () => saveFlow(true)
|
||||
}
|
||||
]
|
||||
|
||||
if (initialPath != '') {
|
||||
dropdownItems.push({
|
||||
label: 'Fork',
|
||||
onClick: () => window.open(`/flows/add?template=${initialPath}`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
@@ -261,10 +290,17 @@
|
||||
<div class="flex flex-col flex-1 h-screen">
|
||||
<!-- Nav between steps-->
|
||||
<div
|
||||
class="justify-between flex flex-row w-full items-center pl-2.5 pr-6 space-x-4 overflow-x-auto scrollbar-hidden max-h-12 h-full"
|
||||
class="justify-between flex flex-row items-center pl-2.5 pr-6 space-x-4 scrollbar-hidden max-h-12 h-full"
|
||||
>
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<FlowImportExportMenu />
|
||||
<div class="flex w-full max-w-md gap-4 items-center">
|
||||
<div class="min-w-64 w-full">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Flow summary"
|
||||
class="text-sm w-full font-semibold"
|
||||
bind:value={$flowStore.summary}
|
||||
/>
|
||||
</div>
|
||||
<UndoRedo
|
||||
undoProps={{ disabled: $history.index === 0 }}
|
||||
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
|
||||
@@ -278,7 +314,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="gap-1 flex-row hidden md:flex shrink overflow-hidden">
|
||||
<div class="gap-4 flex-row hidden md:flex w-full max-w-md">
|
||||
{#if $scheduleStore.enabled}
|
||||
<Button
|
||||
btnClasses="hidden lg:inline-flex"
|
||||
@@ -293,53 +329,47 @@
|
||||
{$scheduleStore.cron ?? ''}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
btnClasses="hidden lg:inline-flex"
|
||||
startIcon={{ icon: faPen }}
|
||||
variant="contained"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
select('settings-metadata')
|
||||
document.getElementById('path')?.focus()
|
||||
}}
|
||||
>
|
||||
{$flowStore.path && $flowStore.path != '' ? $flowStore.path : 'Choose a path'}
|
||||
</Button>
|
||||
<Button
|
||||
startIcon={{ icon: faPen }}
|
||||
variant="contained"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
select('settings-metadata')
|
||||
document.getElementById('flow-summary')?.focus()
|
||||
}}
|
||||
>
|
||||
<div class="max-w-[10em] !truncate">
|
||||
{$flowStore.summary == '' || !$flowStore.summary ? 'No summary' : $flowStore.summary}
|
||||
<div class="flex justify-start w-full">
|
||||
<div>
|
||||
<button
|
||||
on:click={async () => {
|
||||
select('settings-metadata')
|
||||
document.getElementById('path')?.focus()
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-gray-300 !text-gray-600 !h-[28px] !w-[70px] rounded-r-none"
|
||||
>
|
||||
<Pen size={12} class="mr-2" /> Path
|
||||
</Badge>
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={$flowStore.path && $flowStore.path != '' ? $flowStore.path : 'Choose a path'}
|
||||
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row space-x-2">
|
||||
<FlowImportExportMenu />
|
||||
|
||||
<FlowPreviewButtons />
|
||||
<div class="center-center">
|
||||
<ButtonPopup
|
||||
<Button
|
||||
loading={loadingSave}
|
||||
size="sm"
|
||||
size="xs"
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={() => saveFlow(false)}
|
||||
{dropdownItems}
|
||||
>
|
||||
<svelte:fragment slot="main">Save</svelte:fragment>
|
||||
<ButtonPopupItem on:click={() => saveFlow(true)}>Save and exit</ButtonPopupItem>
|
||||
{#if initialPath != ''}
|
||||
<ButtonPopupItem
|
||||
on:click={() => {
|
||||
window.open(`/flows/add?template=${initialPath}`)
|
||||
}}>Fork</ButtonPopupItem
|
||||
>
|
||||
{/if}
|
||||
</ButtonPopup>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
}
|
||||
export let overflowAuto = false
|
||||
export let noSide = false
|
||||
export let download = false
|
||||
|
||||
let stepDetail: FlowModule | string | undefined = undefined
|
||||
let codeViewer: Drawer
|
||||
let topHeight = 0
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={codeViewer} size="900px">
|
||||
@@ -48,7 +48,7 @@
|
||||
<div class="mt-6">
|
||||
<h3>Code</h3>
|
||||
<iframe
|
||||
class="w-full h-full text-sm"
|
||||
class="w-full h-full text-sm"
|
||||
title="embedded script from hub"
|
||||
frameborder="0"
|
||||
src="https://hub.windmill.dev/embed/script/{stepDetail.value?.path?.substring(4)}"
|
||||
@@ -77,6 +77,7 @@
|
||||
class:overflow-auto={overflowAuto}
|
||||
>
|
||||
<FlowGraph
|
||||
{download}
|
||||
minHeight={400}
|
||||
modules={flow?.value?.modules}
|
||||
failureModule={flow?.value?.failure_module}
|
||||
@@ -141,7 +142,7 @@
|
||||
>
|
||||
</h3>
|
||||
<iframe
|
||||
class="w-full h-full text-sm"
|
||||
class="w-full h-full text-sm"
|
||||
title="embedded script from hub"
|
||||
frameborder="0"
|
||||
src="https://hub.windmill.dev/embed/script/{stepDetail.value?.path?.substring(4)}"
|
||||
@@ -154,11 +155,7 @@
|
||||
json={stepDetail.value.iterator.value}
|
||||
/>{:else}
|
||||
<span class="text-xs">
|
||||
<Highlight
|
||||
offsetTop={0}
|
||||
language={typescript}
|
||||
code={cleanExpr(stepDetail.value.iterator.expr)}
|
||||
/>
|
||||
<Highlight language={typescript} code={cleanExpr(stepDetail.value.iterator.expr)} />
|
||||
</span>
|
||||
{/if}</p
|
||||
>
|
||||
|
||||
@@ -95,12 +95,14 @@
|
||||
|
||||
<div class="flex divide-y flex-col space-y-2 h-screen bg-white px-6 py-2 w-full">
|
||||
<div class="flex flex-row justify-between w-full items-center gap-x-2">
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="hover:bg-gray-200 bg-gray-100 rounded-full w-16 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<Icon data={faClose} class="text-gray-500" />
|
||||
</button>
|
||||
<div class="w-8">
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="hover:bg-gray-200 bg-gray-100 rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<Icon data={faClose} class="text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if isRunning}
|
||||
<Button
|
||||
@@ -136,17 +138,17 @@
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
btnClasses="h-full"
|
||||
btnClasses="h-full truncate"
|
||||
size="sm"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
capturePayload.openDrawer()
|
||||
}}>Fill test args from a request</Button
|
||||
}}>Fill args from a request</Button
|
||||
>
|
||||
</div>
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} />
|
||||
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 pr-4">
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 pr-4">
|
||||
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
|
||||
@@ -173,6 +173,34 @@
|
||||
return arg == true
|
||||
}
|
||||
}
|
||||
|
||||
function onJobsLoaded(mod: FlowStatusModule, job: Job): void {
|
||||
if (mod.id && (mod.flow_jobs ?? []).length == 0) {
|
||||
if (flowState && flowState[mod.id]) {
|
||||
flowState[mod.id].previewResult = job['result']
|
||||
flowState[mod.id].previewArgs = job.args
|
||||
}
|
||||
if (job.type == 'QueuedJob') {
|
||||
localFlowModuleStates[mod.id] = {
|
||||
type: FlowStatusModule.type.IN_PROGRESS,
|
||||
logs: job.logs,
|
||||
args: job.args,
|
||||
parent_module: mod['parent_module']
|
||||
}
|
||||
} else {
|
||||
localFlowModuleStates[mod.id] = {
|
||||
args: job.args,
|
||||
type: job['success'] ? FlowStatusModule.type.SUCCESS : FlowStatusModule.type.FAILURE,
|
||||
logs: job.logs,
|
||||
result: job['result'],
|
||||
job_id: job.id,
|
||||
parent_module: mod['parent_module'],
|
||||
iteration_total: mod.iterator?.itered?.length
|
||||
// retries: flowState?.raw_flow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if job}
|
||||
@@ -379,49 +407,31 @@
|
||||
<div class="line w-8 h-10" />
|
||||
<li class="w-full border border-gray-600 p-6 space-y-2 bg-blue-50/50">
|
||||
{#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)}
|
||||
<svelte:self
|
||||
{workspaceId}
|
||||
bind:suspend_status
|
||||
bind:retry_status
|
||||
bind:flowState
|
||||
bind:flowModuleStates={localFlowModuleStates}
|
||||
jobId={mod.job}
|
||||
flowJobIds={mod.flow_jobs
|
||||
? {
|
||||
moduleId: mod.id,
|
||||
flowJobs: mod.flow_jobs
|
||||
}
|
||||
: undefined}
|
||||
on:jobsLoaded={(e) => {
|
||||
if (mod.id && (mod.flow_jobs ?? []).length == 0) {
|
||||
if (flowState && flowState[mod.id]) {
|
||||
flowState[mod.id].previewResult = e.detail.result
|
||||
flowState[mod.id].previewArgs = e.detail.args
|
||||
}
|
||||
if (e.detail.type == 'QueuedJob') {
|
||||
localFlowModuleStates[mod.id] = {
|
||||
type: FlowStatusModule.type.IN_PROGRESS,
|
||||
logs: e.detail.logs,
|
||||
args: e.detail.args,
|
||||
parent_module: mod['parent_module']
|
||||
}
|
||||
} else {
|
||||
localFlowModuleStates[mod.id] = {
|
||||
args: e.detail.args,
|
||||
type: e.detail.success
|
||||
? FlowStatusModule.type.SUCCESS
|
||||
: FlowStatusModule.type.FAILURE,
|
||||
logs: e.detail.logs,
|
||||
result: e.detail.result,
|
||||
job_id: e.detail.id,
|
||||
parent_module: mod['parent_module'],
|
||||
iteration_total: mod.iterator?.itered?.length
|
||||
// retries: flowState?.raw_flow
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if job.raw_flow?.modules[i]?.value.type == 'flow'}
|
||||
<svelte:self
|
||||
{workspaceId}
|
||||
jobId={mod.job}
|
||||
bind:suspend_status
|
||||
bind:retry_status
|
||||
on:jobsLoaded={(e) => onJobsLoaded(mod, e.detail)}
|
||||
/>
|
||||
{:else}
|
||||
<svelte:self
|
||||
{workspaceId}
|
||||
bind:suspend_status
|
||||
bind:retry_status
|
||||
bind:flowState
|
||||
bind:flowModuleStates={localFlowModuleStates}
|
||||
jobId={mod.job}
|
||||
flowJobIds={mod.flow_jobs
|
||||
? {
|
||||
moduleId: mod.id,
|
||||
flowJobs: mod.flow_jobs
|
||||
}
|
||||
: undefined}
|
||||
on:jobsLoaded={(e) => onJobsLoaded(mod, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<ModuleStatus
|
||||
type={mod.type}
|
||||
@@ -492,7 +502,7 @@
|
||||
<p class="p-2">No arguments</p>
|
||||
{/if}
|
||||
{:else if node}
|
||||
<div class="px-2 flex gap-2 min-w-0 ">
|
||||
<div class="px-2 flex gap-2 min-w-0">
|
||||
<ModuleStatus type={node.type} scheduled_for={node['scheduled_for']} />
|
||||
{#if node.job_id}
|
||||
<div class="truncate"
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
{:else}
|
||||
<div class="text-gray-700 text-xs italic mb-4">No inputs</div>
|
||||
{/if}
|
||||
<FlowGraphViewer {noSide} {flow} overflowAuto />
|
||||
<FlowGraphViewer download {noSide} {flow} overflowAuto />
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value="json">
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
type Role = 'viewer' | 'writer' | 'admin'
|
||||
let folder: Folder | undefined
|
||||
let perms: { owner_name: string; role: Role }[] | undefined = undefined
|
||||
let managing_folders: string[] = []
|
||||
let usernames: string[] = []
|
||||
let groups: string[] = []
|
||||
let ownerItem: string = ''
|
||||
let folders: string[] = []
|
||||
|
||||
async function loadUsernames(): Promise<void> {
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
|
||||
@@ -42,14 +40,9 @@
|
||||
async function load() {
|
||||
loadUsernames()
|
||||
loadGroups()
|
||||
loadFolders()
|
||||
await loadFolder()
|
||||
}
|
||||
|
||||
async function loadFolders(): Promise<void> {
|
||||
folders = (await FolderService.listFolders({ workspace: $workspaceStore! })).map((x) => x.name)
|
||||
}
|
||||
|
||||
async function addToFolder() {
|
||||
await GranularAclService.addGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
@@ -67,7 +60,8 @@
|
||||
can_write =
|
||||
$userStore != undefined &&
|
||||
(folder?.owners.includes('u/' + $userStore.username) ||
|
||||
($userStore.is_admin ?? false) || ($userStore.is_super_admin ?? false) ||
|
||||
($userStore.is_admin ?? false) ||
|
||||
($userStore.is_super_admin ?? false) ||
|
||||
$userStore.pgroups.findIndex((x) => folder?.owners.includes(x)) != -1)
|
||||
|
||||
perms = Array.from(
|
||||
@@ -82,9 +76,6 @@
|
||||
role: getRole(x)
|
||||
}
|
||||
})
|
||||
managing_folders = Object.entries(folder?.extra_perms ?? {})
|
||||
.filter(([k, v]) => k.startsWith('g/') && v)
|
||||
.map(([k, v]) => k)
|
||||
}
|
||||
|
||||
function getRole(x: string): Role {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FolderService, GroupService } from '$lib/gen'
|
||||
import { FolderService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
export let name: string
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { GranularAclService, GroupService, UserService, type Group } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { type Group, GroupService, UserService, GranularAclService } from '$lib/gen'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { canWrite, sendUserToast } from '$lib/utils'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import { Button, ToggleButton, ToggleButtonGroup } from './common'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let name: string
|
||||
let can_write = false
|
||||
@@ -16,10 +16,8 @@
|
||||
type Role = 'member' | 'manager' | 'admin'
|
||||
let group: Group | undefined
|
||||
let members: { member_name: string; role: Role }[] | undefined = undefined
|
||||
let managing_groups: string[] = []
|
||||
let usernames: string[] | undefined = []
|
||||
let username: string = ''
|
||||
let groups: string[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -34,15 +32,10 @@
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loadGroups()
|
||||
await loadGroup()
|
||||
loadUsernames()
|
||||
}
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
groups = (await GroupService.listGroups({ workspace: $workspaceStore! })).map((x) => x.name)
|
||||
}
|
||||
|
||||
async function addToGroup() {
|
||||
await GroupService.addUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
@@ -68,9 +61,6 @@
|
||||
role: getRole(x)
|
||||
}
|
||||
})
|
||||
managing_groups = Object.entries(group?.extra_perms ?? {})
|
||||
.filter(([k, v]) => k.startsWith('g/') && v)
|
||||
.map(([k, v]) => k)
|
||||
}
|
||||
|
||||
function getRole(x: string): Role {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</script>
|
||||
|
||||
{#if code?.length < 5000}
|
||||
<Highlight className="nowrap {$$props.class}" language={lang} {code} />
|
||||
<Highlight class="nowrap {$$props.class}" language={lang} {code} />
|
||||
{:else}
|
||||
<pre class="overflow-auto max-h-screen {$$props.class}"
|
||||
><code class="language-{language}">{code}</code></pre
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
export let content: string
|
||||
</script>
|
||||
|
||||
<pre class="bg-gray-700 text-gray-100 p-2 font-mono text-sm whitespace-pre-wrap"
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<pre class="bg-gray-700 text-gray-100 p-2 font-mono text-sm whitespace-pre-wrap"
|
||||
>{content} <span on:click={() => copyToClipboard(content)} class="cursor-pointer ml-2"
|
||||
><Icon data={faClipboard} /></span
|
||||
></pre
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import TemplateEditor from './TemplateEditor.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { setInputCat as computeInputCat } from '$lib/utils'
|
||||
|
||||
export let schema: Schema
|
||||
@@ -40,17 +39,24 @@
|
||||
schema.properties[argName].enum,
|
||||
schema.properties[argName].contentEncoding
|
||||
)
|
||||
|
||||
let propertyType = getPropertyType(arg)
|
||||
|
||||
function getPropertyType(arg: InputTransform | any): 'static' | 'javascript' {
|
||||
let type: 'static' | 'javascript' = arg?.type ?? 'static'
|
||||
if (type == 'javascript') {
|
||||
|
||||
if (
|
||||
type == 'javascript' &&
|
||||
isStaticTemplate(inputCat) &&
|
||||
(arg?.expr?.length === 0 || arg?.expr?.[0] === '`')
|
||||
) {
|
||||
const newValue = codeToStaticTemplate(arg.expr)
|
||||
if (newValue) {
|
||||
type = 'static'
|
||||
arg.value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
@@ -112,8 +118,6 @@
|
||||
const { focusProp, propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||
|
||||
$: isStaticTemplate(inputCat) && propertyType == 'static' && setPropertyType(arg?.value)
|
||||
const openBracket = '${'
|
||||
const closeBracket = '}'
|
||||
|
||||
function setDefaultCode() {
|
||||
if (!arg?.value) {
|
||||
@@ -148,86 +152,89 @@
|
||||
</div>
|
||||
{#if !noDynamicToggle}
|
||||
<div class="flex flex-row gap-x-4 gap-y-1 flex-wrap z-10">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={propertyType}
|
||||
on:selected={(e) => {
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
if (e.detail === 'javascript') {
|
||||
if (arg.expr == undefined) {
|
||||
arg.expr = getDefaultExpr(
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate
|
||||
? `\`${arg?.value?.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
: arg.value
|
||||
? JSON.stringify(arg?.value, null, 4)
|
||||
: ''
|
||||
)
|
||||
}
|
||||
if (arg) {
|
||||
arg.value = undefined
|
||||
}
|
||||
propertyType = 'javascript'
|
||||
arg.type = 'javascript'
|
||||
} else {
|
||||
if (staticTemplate) {
|
||||
if (arg) {
|
||||
arg.value = codeToStaticTemplate(arg.expr)
|
||||
arg.expr = undefined
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
bind:selected={propertyType}
|
||||
on:selected={(e) => {
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
if (e.detail === 'javascript') {
|
||||
if (arg.expr == undefined) {
|
||||
arg.expr = getDefaultExpr(
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate
|
||||
? `\`${arg?.value?.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
: arg.value
|
||||
? JSON.stringify(arg?.value, null, 4)
|
||||
: ''
|
||||
)
|
||||
}
|
||||
setPropertyType(arg?.value)
|
||||
} else {
|
||||
if (arg) {
|
||||
arg.type = 'static'
|
||||
arg.value = undefined
|
||||
arg.expr = undefined
|
||||
arg.type = 'javascript'
|
||||
}
|
||||
propertyType = 'javascript'
|
||||
} else {
|
||||
if (staticTemplate) {
|
||||
if (arg) {
|
||||
arg.value = codeToStaticTemplate(arg.expr)
|
||||
arg.expr = undefined
|
||||
}
|
||||
setPropertyType(arg?.value)
|
||||
} else {
|
||||
if (arg) {
|
||||
arg.type = 'static'
|
||||
arg.value = undefined
|
||||
arg.expr = undefined
|
||||
}
|
||||
}
|
||||
propertyType = 'static'
|
||||
}
|
||||
propertyType = 'static'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton light position="left" value="static" size="xs">
|
||||
{'${} '}
|
||||
<Tooltip
|
||||
>Write text or surround javascript with "{openBracket}" and "{closeBracket}". Use
|
||||
`result` to connect to another node's output.
|
||||
</Tooltip></ToggleButton
|
||||
>
|
||||
{:else}
|
||||
<ToggleButton light position="left" value="static" size="xs">Static</ToggleButton>
|
||||
{/if}
|
||||
|
||||
<ToggleButton
|
||||
light
|
||||
position="right"
|
||||
value="javascript"
|
||||
startIcon={{ icon: faCode }}
|
||||
size="xs"
|
||||
> <Tooltip
|
||||
>Write javascript expressions directly, using 'flow_input' or 'result'. You can use
|
||||
multiline javascript.
|
||||
</Tooltip></ToggleButton
|
||||
}}
|
||||
>
|
||||
</ToggleButtonGroup>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
return true
|
||||
})
|
||||
}}>Connect →</Button
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton
|
||||
title={`Write text or surround javascript with \`\$\{\` and \`\}\`. Use \`result\` to connect to another node\'s output.`}
|
||||
light
|
||||
position="left"
|
||||
value="static"
|
||||
size="xs2"
|
||||
>
|
||||
{'${} '}
|
||||
</ToggleButton>
|
||||
{:else}
|
||||
<ToggleButton light position="left" value="static" size="xs2">Static</ToggleButton>
|
||||
{/if}
|
||||
|
||||
<ToggleButton
|
||||
light
|
||||
title="Write javascript expressions directly, using 'flow_input' or 'result'. You can use multiline javascript."
|
||||
position="right"
|
||||
value="javascript"
|
||||
startIcon={{ icon: faCode }}
|
||||
size="xs2"
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
title="Connect to another node's output"
|
||||
variant="border"
|
||||
color="dark"
|
||||
size="xs2"
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
return true
|
||||
})
|
||||
}}>Link →</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="max-w-xs" />
|
||||
|
||||
<div
|
||||
class="relative mb-6 {$propPickerConfig?.propName == argName
|
||||
? 'outline outline-offset-0 outline-2 outline-blue-500 rounded-md'
|
||||
@@ -241,7 +248,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
|
||||
<div class="py-1 mt-2 min-h-[28px] rounded border border-1 border-gray-500">
|
||||
<div class="mt-2 min-h-[28px] rounded border border-1 border-gray-500">
|
||||
{#if arg}
|
||||
<TemplateEditor
|
||||
bind:this={monacoTemplate}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
<div class="w-full {clazz}">
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName, i (argName)}
|
||||
{#each keys as argName (argName)}
|
||||
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
|
||||
<div class="z-10">
|
||||
<InputTransformForm
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
json={val.value}
|
||||
/>{:else}
|
||||
<span class="text-xs">
|
||||
<Highlight offsetTop={0} language={typescript} code={cleanExpr(val.expr)} />
|
||||
<Highlight language={typescript} code={cleanExpr(val.expr)} />
|
||||
</span>
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
@@ -314,5 +314,14 @@
|
||||
{/if}
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
{#if error && error != ''}
|
||||
<div class="text-right text-xs text-red-600">
|
||||
{#if error === ''}
|
||||
|
||||
{:else}
|
||||
{error}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#each Object.keys(schema.properties ?? {}) as argName, i (argName)}
|
||||
{#each Object.keys(schema.properties ?? {}) as argName (argName)}
|
||||
<div>
|
||||
{#if typeof args == 'object' && schema?.properties[argName] && args}
|
||||
<LightweightArgInput
|
||||
|
||||
@@ -65,11 +65,11 @@
|
||||
<p class="font-semibold text-gray-800"><slot name="title" /></p>
|
||||
<div />
|
||||
</div>
|
||||
<div class="flex flex-col bg-gray-50 pt-3 px-6 grow overflow-y-auto">
|
||||
<div class="flex flex-col bg-gray-50 pt-3 px-6 grow overflow-y-auto">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
<div class="flex flex-col bg-white border-gray-200 p-2">
|
||||
<div class="flex flex-row justify-between p-2 ">
|
||||
<div class="flex flex-row justify-between p-2">
|
||||
<button
|
||||
on:click={() => {
|
||||
closeDrawer()
|
||||
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.blurred-background {
|
||||
/* @apply absolute sm:top-6 lg:top-8 left-28 sm:left-40 md:left-48; */ /* If we wanted to make the navbars visible */
|
||||
@apply fixed top-0 left-0;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import { ScriptService, type FlowModule, type InputTransform, type Job } from '$lib/gen'
|
||||
import { getScriptByPath, sendUserToast, truncateRev } from '$lib/utils'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import RunForm from './RunForm.svelte'
|
||||
import TestJobLoader from './TestJobLoader.svelte'
|
||||
import LogViewer from './LogViewer.svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { getScriptByPath } from '$lib/utils'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import LogViewer from './LogViewer.svelte'
|
||||
import RunForm from './RunForm.svelte'
|
||||
import TestJobLoader from './TestJobLoader.svelte'
|
||||
|
||||
export let mod: FlowModule
|
||||
export let schema: Schema
|
||||
@@ -43,14 +43,14 @@
|
||||
|
||||
export async function runTest(args: any) {
|
||||
const val = mod.value
|
||||
let jobId: string | undefined = undefined
|
||||
// let jobId: string | undefined = undefined
|
||||
if (val.type == 'rawscript') {
|
||||
jobId = await testJobLoader?.runPreview(val.path, val.content, val.language, args)
|
||||
await testJobLoader?.runPreview(val.path, val.content, val.language, args)
|
||||
} else if (val.type == 'script') {
|
||||
const script = val.hash
|
||||
? await ScriptService.getScriptByHash({ workspace: $workspaceStore!, hash: val.hash })
|
||||
: await getScriptByPath(val.path)
|
||||
jobId = await testJobLoader?.runPreview(val.path, script.content, script.language, args)
|
||||
await testJobLoader?.runPreview(val.path, script.content, script.language, args)
|
||||
} else {
|
||||
throw Error('not testable module type')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { FlowStatusModule } from '$lib/gen'
|
||||
import { displayDate } from '$lib/utils'
|
||||
import { faHourglassHalf } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import { Badge } from './common'
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
</script>
|
||||
|
||||
<div class="multiselect" class:readonly>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="tokens" class:showOptions on:click={handleTokenClick}>
|
||||
{#each Object.values(selected) as s}
|
||||
<div class="token" data-id={s.value}>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService } from '$lib/gen'
|
||||
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
|
||||
export let format: string
|
||||
@@ -12,26 +9,6 @@
|
||||
}
|
||||
|
||||
let path: string = ''
|
||||
let args: Record<string, any> = {}
|
||||
|
||||
let schema: any | undefined = undefined
|
||||
let resourceTypeName: string = ''
|
||||
|
||||
$: format.startsWith('resource-') && loadSchema(format)
|
||||
|
||||
async function loadSchema(format: string) {
|
||||
resourceTypeName = format.substring('resource-'.length)
|
||||
try {
|
||||
schema = (
|
||||
await ResourceService.getResourceType({
|
||||
workspace: $workspaceStore!,
|
||||
path: resourceTypeName
|
||||
})
|
||||
).schema
|
||||
} catch (e) {
|
||||
schema = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function resourceToValue() {
|
||||
if (path) {
|
||||
@@ -46,10 +23,6 @@
|
||||
}
|
||||
|
||||
function valueToPath() {
|
||||
if (!isString(value) && value) {
|
||||
args = value
|
||||
}
|
||||
|
||||
if (isResource()) {
|
||||
path = value.substr('$res:'.length)
|
||||
}
|
||||
@@ -60,7 +33,6 @@
|
||||
|
||||
<div class="flex flex-row w-full flex-wrap gap-x-2 gap-y-0.5">
|
||||
<ResourcePicker
|
||||
on:refresh={() => loadSchema(format)}
|
||||
on:change={(e) => {
|
||||
path = e.detail
|
||||
resourceToValue()
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
<script lang="ts" context="module">
|
||||
const lastMetaUsed = writable<Meta | undefined>(undefined)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { type Meta, pathToMeta } from '$lib/common'
|
||||
import { pathToMeta, type Meta } from '$lib/common'
|
||||
|
||||
import {
|
||||
AppService,
|
||||
FlowService,
|
||||
FolderService,
|
||||
GroupService,
|
||||
ResourceService,
|
||||
ScheduleService,
|
||||
ScriptService,
|
||||
VariableService
|
||||
} from '$lib/gen'
|
||||
import { superadmin, userStore, workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Required from './Required.svelte'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import { faEye, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import ToggleButtonGroup from './common/toggleButton/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from './common/toggleButton/ToggleButton.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import { writable } from 'svelte/store'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import ToggleButton from './common/toggleButton/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton/ToggleButtonGroup.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import Required from './Required.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -40,8 +43,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let folders: string[] = []
|
||||
let groups: string[] = []
|
||||
let folders: { name: string; write: boolean }[] = []
|
||||
|
||||
$: meta && onMetaChange()
|
||||
|
||||
@@ -49,6 +51,10 @@
|
||||
if (meta) {
|
||||
path = metaToPath(meta)
|
||||
validate(meta, path, kind)
|
||||
$lastMetaUsed = {
|
||||
...meta,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +77,17 @@
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
if ($lastMetaUsed == undefined) {
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
}
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0].replace(/[^a-zA-Z0-9]/g, '')
|
||||
} else {
|
||||
meta = { ...$lastMetaUsed, name: random_adj() + '_' + namePlaceholder }
|
||||
}
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0]
|
||||
|
||||
let newMeta = { ...meta }
|
||||
while (await pathExists(metaToPath(newMeta), kind)) {
|
||||
disabled = true
|
||||
@@ -95,30 +104,26 @@
|
||||
}
|
||||
|
||||
async function loadFolders(): Promise<void> {
|
||||
let initialFolders: string[] = []
|
||||
let initialFolders: { name: string; write: boolean }[] = []
|
||||
let initialFolder = ''
|
||||
if (initialPath?.split('/')?.[0] == 'f') {
|
||||
initialFolder = initialPath?.split('/')?.[1]
|
||||
initialFolders.push(initialFolder)
|
||||
initialFolders.push({ name: initialFolder, write: true })
|
||||
}
|
||||
folders = initialFolders.concat(
|
||||
(
|
||||
await FolderService.listFolderNames({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
).filter((x) => x != initialFolder)
|
||||
)
|
||||
}
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
let initialGroups: string[] = []
|
||||
if (initialPath?.split('/')?.[0] == 'f') {
|
||||
initialGroups.push(initialPath?.split('/')?.[1])
|
||||
}
|
||||
groups = initialGroups.concat(
|
||||
await GroupService.listGroupNames({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
)
|
||||
.filter((x) => x != initialFolder)
|
||||
.map((x) => ({
|
||||
name: x,
|
||||
write:
|
||||
($userStore?.folders?.includes(x) == true ?? false) ||
|
||||
($userStore?.is_admin ?? false) ||
|
||||
($userStore?.is_super_admin ?? false)
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -194,7 +199,6 @@
|
||||
$: {
|
||||
if ($workspaceStore && $userStore) {
|
||||
loadFolders()
|
||||
loadGroups()
|
||||
initPath()
|
||||
}
|
||||
}
|
||||
@@ -224,56 +228,14 @@
|
||||
requestBody: { name: newFolderName }
|
||||
})
|
||||
folderCreated = newFolderName
|
||||
$userStore?.folders?.push(newFolderName)
|
||||
loadFolders()
|
||||
if (meta) {
|
||||
meta.owner = newFolderName
|
||||
}
|
||||
loadFolders()
|
||||
}
|
||||
|
||||
let newGroup: Drawer
|
||||
let viewGroup: Drawer
|
||||
let newGroupName: string
|
||||
let groupCreated: string | undefined = undefined
|
||||
|
||||
async function addGroup() {
|
||||
await GroupService.createGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { name: newGroupName }
|
||||
})
|
||||
groupCreated = newGroupName
|
||||
if (meta) {
|
||||
meta.owner = newGroupName
|
||||
}
|
||||
loadGroups()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={newGroup}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
on:close={() => {
|
||||
newGroup.closeDrawer()
|
||||
groupCreated = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<input class="mr-2" placeholder="New group name" bind:value={newGroupName} />
|
||||
<Button size="md" endIcon={{ icon: faPlus }} disabled={!newGroupName} on:click={addGroup}>
|
||||
New group
|
||||
</Button>
|
||||
</div>
|
||||
{#if groupCreated}
|
||||
<div class="mt-8" />
|
||||
<GroupEditor name={groupCreated} />
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<Drawer bind:this={viewGroup}>
|
||||
<DrawerContent title="Folder {meta?.owner}" on:close={viewGroup.closeDrawer}>
|
||||
<GroupEditor name={meta?.owner ?? ''} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Drawer bind:this={newFolder}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
@@ -310,6 +272,7 @@
|
||||
<div class="flex flex-col sm:flex-row items-center gap-2 sm:gap-4 pb-0 mb-1">
|
||||
{#if meta != undefined}
|
||||
<div class="flex gap-4 shrink">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block">
|
||||
<span class="text-gray-700 text-sm whitespace-nowrap">Owner</span>
|
||||
|
||||
@@ -356,8 +319,8 @@
|
||||
|
||||
<div class="flex flex-row gap-1 w-full">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#each folders as f}
|
||||
<option>{f}</option>
|
||||
{#each folders as { name, write }}
|
||||
<option disabled={!write}>{name}{write ? '' : ' (read-only)'}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button variant="border" size="xs" on:click={viewFolder.openDrawer}>
|
||||
@@ -368,27 +331,6 @@
|
||||
></div
|
||||
>
|
||||
</label>
|
||||
{:else if meta.ownerKind === 'group'}
|
||||
<label class="block grow w-48">
|
||||
<span class="text-gray-700 text-sm"
|
||||
>Group <Tooltip>Item will be owned by the group and hence all its member</Tooltip
|
||||
></span
|
||||
>
|
||||
|
||||
<div class="flex flex-row gap-1">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#each groups as g}
|
||||
<option>{g}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button variant="border" size="xs" on:click={viewGroup.openDrawer}>
|
||||
<Icon scale={0.8} data={faEye} /></Button
|
||||
>
|
||||
<Button variant="border" size="xs" on:click={newGroup.openDrawer}>
|
||||
<Icon scale={0.8} data={faPlus} /></Button
|
||||
></div
|
||||
>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
<label class="block grow w-full max-w-md">
|
||||
@@ -396,6 +338,7 @@
|
||||
Name
|
||||
<Required required={true} />
|
||||
</span>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
{disabled}
|
||||
type="text"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createPopperActions } from 'svelte-popperjs'
|
||||
import { createPopperActions, type PopperOptions } from 'svelte-popperjs'
|
||||
import type { PopoverPlacement } from './Popover.model'
|
||||
|
||||
export let placement: PopoverPlacement = 'auto'
|
||||
@@ -9,28 +9,16 @@
|
||||
export let disapperTimoout = 100
|
||||
|
||||
const [popperRef, popperContent] = createPopperActions({ placement })
|
||||
const betterPreventOverflow = (options) => ({
|
||||
name: 'preventOverflow',
|
||||
options,
|
||||
effect: ({ state }) => {
|
||||
const { padding = 0 } = options
|
||||
|
||||
state.elements.popper.style.maxWidth = `calc(100vw - ${padding * 2}px)`
|
||||
}
|
||||
})
|
||||
const extraOpts = {
|
||||
const popperOptions: PopperOptions<{}> = {
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed',
|
||||
modifiers: [
|
||||
betterPreventOverflow({ padding: 10 }),
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [8, 8]
|
||||
}
|
||||
},
|
||||
{ name: 'offset', options: { offset: [8, 8] } },
|
||||
{
|
||||
name: 'arrow',
|
||||
options: {
|
||||
padding: 10 // 5px from the edges of the popper
|
||||
padding: 10
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -59,10 +47,10 @@
|
||||
{/if}
|
||||
{#if showTooltip && !disablePopup}
|
||||
<div
|
||||
use:popperContent={extraOpts}
|
||||
use:popperContent={popperOptions}
|
||||
on:mouseenter={open}
|
||||
on:mouseleave={close}
|
||||
class="z-50 py-2 px-3 rounded-md text-sm font-normal !text-gray-300 bg-gray-800
|
||||
class="z-50 py-2 px-3 rounded-md text-sm font-normal !text-gray-300 bg-gray-800
|
||||
whitespace-normal text-left {popupClass}"
|
||||
>
|
||||
<div class="max-w-sm">
|
||||
|
||||
@@ -51,10 +51,6 @@
|
||||
onDragStart(e)
|
||||
}
|
||||
|
||||
function onHover(e) {
|
||||
thumbHover = thumbHover ? false : true
|
||||
}
|
||||
|
||||
function onDragStart(e) {
|
||||
// If mouse event add a pointer events shield
|
||||
if (e.type === 'mousedown') document.body.append(mouseEventShield)
|
||||
@@ -182,6 +178,7 @@
|
||||
>
|
||||
<div class="range__track" bind:this={container}>
|
||||
<div class="range__track--highlighted" bind:this={progressBar} />
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
class="range__thumb"
|
||||
class:range__thumb--holding={holding}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { type Resource, ResourceService, type ResourceType, VariableService } from '$lib/gen'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { ResourceService, type Resource } from '$lib/gen'
|
||||
import { canWrite, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { Alert, Button, Drawer, Skeleton } from './common'
|
||||
import Path from './Path.svelte'
|
||||
import Required from './Required.svelte'
|
||||
import { Alert, Button, Drawer, Skeleton } from './common'
|
||||
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import autosize from 'svelte-autosize'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
|
||||
let path = ''
|
||||
@@ -131,7 +131,7 @@
|
||||
on:close={drawer.closeDrawer}
|
||||
>
|
||||
<div>
|
||||
<div class="flex flex-col gap-3 py-3 text-gray-700">
|
||||
<div class="flex flex-col gap-3 py-3 text-gray-700">
|
||||
<div>
|
||||
{#if !can_write}
|
||||
<div class="m-2">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService, type Resource } from '$lib/gen'
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { faPen, faPlus, faRotateRight } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { Button } from './common'
|
||||
import Select from 'svelte-select'
|
||||
import AppConnect from './AppConnect.svelte'
|
||||
import ResourceEditor from './ResourceEditor.svelte'
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
|
||||
import AppConnect from './AppConnect.svelte'
|
||||
import { Button } from './common'
|
||||
import ResourceEditor from './ResourceEditor.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import {
|
||||
decodeArgs,
|
||||
defaultIfEmptyString,
|
||||
displayDaysAgo,
|
||||
emptyString,
|
||||
@@ -10,16 +8,16 @@
|
||||
} from '$lib/utils'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { runFormStore, userStore } from '$lib/stores'
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import CliHelpBox from './CliHelpBox.svelte'
|
||||
import { Badge, Button } from './common'
|
||||
import InlineCodeCopy from './InlineCodeCopy.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SharedBadge from './SharedBadge.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { runFormStore, userStore } from '$lib/stores'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import CliHelpBox from './CliHelpBox.svelte'
|
||||
import InlineCodeCopy from './InlineCodeCopy.svelte'
|
||||
|
||||
export let runnable:
|
||||
| {
|
||||
|
||||
@@ -7,26 +7,26 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { sendUserToast, formatCron, canWrite, emptyString } from '$lib/utils'
|
||||
import { ScriptService, Script, ScheduleService, type Flow, FlowService } from '$lib/gen'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { Alert, Badge, Button } from '$lib/components/common'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import Required from '$lib/components/Required.svelte'
|
||||
import CronInput, { OFFSET } from '$lib/components/CronInput.svelte'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import CronInput from '$lib/components/CronInput.svelte'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import Required from '$lib/components/Required.svelte'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { FlowService, ScheduleService, Script, ScriptService, type Flow } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, formatCron, sendUserToast } from '$lib/utils'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
let initialPath = ''
|
||||
let edit = true
|
||||
let schedule: string = '0 0 12 * *'
|
||||
let timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
|
||||
let itemKind: 'flow' | 'script' = 'script'
|
||||
|
||||
@@ -37,7 +37,11 @@
|
||||
is_flow = isFlow
|
||||
initialPath = ePath
|
||||
itemKind = is_flow ? 'flow' : 'script'
|
||||
path = ePath
|
||||
if (path == ePath) {
|
||||
loadSchedule()
|
||||
} else {
|
||||
path = ePath
|
||||
}
|
||||
edit = true
|
||||
drawer?.openDrawer()
|
||||
}
|
||||
@@ -49,6 +53,7 @@
|
||||
path = initialScriptPath
|
||||
initialPath = initialScriptPath
|
||||
script_path = initialScriptPath
|
||||
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
drawer?.openDrawer()
|
||||
}
|
||||
|
||||
@@ -91,6 +96,7 @@
|
||||
})
|
||||
enabled = s.enabled
|
||||
schedule = s.schedule
|
||||
timezone = s.timezone
|
||||
script_path = s.script_path ?? ''
|
||||
is_flow = s.is_flow
|
||||
args = s.args ?? {}
|
||||
@@ -107,6 +113,7 @@
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
schedule: formatCron(schedule),
|
||||
timezone,
|
||||
args
|
||||
}
|
||||
})
|
||||
@@ -117,7 +124,7 @@
|
||||
requestBody: {
|
||||
path,
|
||||
schedule: formatCron(schedule),
|
||||
offset: OFFSET,
|
||||
timezone,
|
||||
script_path,
|
||||
is_flow,
|
||||
args,
|
||||
@@ -190,7 +197,8 @@
|
||||
<span class="mr-1">Schedule</span>
|
||||
<Tooltip>Schedules use CRON syntax. Seconds are mandatory.</Tooltip>
|
||||
</h2>
|
||||
<CronInput disabled={!can_write} bind:schedule bind:validCRON />
|
||||
|
||||
<CronInput disabled={!can_write} bind:schedule bind:timezone bind:validCRON />
|
||||
|
||||
<h2 class="border-b pb-1 mt-8 mb-2">Runnable</h2>
|
||||
{#if !edit}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
|
||||
export const ARG_TYPES = ['integer', 'number', 'string', 'boolean', 'object', 'array'] as const
|
||||
export type ArgType = typeof ARG_TYPES[number]
|
||||
export type ArgType = (typeof ARG_TYPES)[number]
|
||||
|
||||
export interface ModalSchemaProperty {
|
||||
selectedType?: string
|
||||
@@ -115,6 +115,7 @@
|
||||
Name
|
||||
<Required required={true} />
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
|
||||
@@ -9,15 +9,16 @@
|
||||
import Path from './Path.svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { Button, ButtonPopup, ButtonPopupItem, Kbd } from './common'
|
||||
import { faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
|
||||
import { Badge, Button, Drawer } from './common'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import SettingSection from './SettingSection.svelte'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import { Pen } from 'lucide-svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import type Editor from './Editor.svelte'
|
||||
|
||||
export let script: Script
|
||||
export let initialPath: string = ''
|
||||
@@ -26,6 +27,16 @@
|
||||
export let lockedLanguage = false
|
||||
export let topHash: string | undefined = undefined
|
||||
|
||||
let metadataOpen = initialPath == '' && $page.url.searchParams.get('state') == undefined
|
||||
let advancedOpen = false
|
||||
|
||||
let editor: Editor | undefined = undefined
|
||||
let scriptEditor: ScriptEditor | undefined = undefined
|
||||
|
||||
export function setCode(code: string): void {
|
||||
editor?.setCode(code)
|
||||
}
|
||||
|
||||
const langs: [string, SupportedLanguage][] = [
|
||||
['Typescript', Script.language.DENO],
|
||||
['Python', Script.language.PYTHON3],
|
||||
@@ -55,12 +66,9 @@
|
||||
]
|
||||
|
||||
let pathError = ''
|
||||
let summaryC: HTMLInputElement | undefined = undefined
|
||||
let pathC: Path | undefined = undefined
|
||||
let loadingSave = false
|
||||
|
||||
$: setQueryWithoutLoad($page.url, [{ key: 'state', value: encodeState(script) }])
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1
|
||||
|
||||
if (script.content == '') {
|
||||
initContent(script.language, script.kind, template)
|
||||
@@ -72,6 +80,7 @@
|
||||
template: 'pgsql' | 'mysql' | 'script'
|
||||
) {
|
||||
script.content = initialCode(language, kind, template)
|
||||
scriptEditor?.inferSchema(script.content, language)
|
||||
}
|
||||
|
||||
async function editScript(leave: boolean): Promise<void> {
|
||||
@@ -104,10 +113,10 @@
|
||||
}
|
||||
})
|
||||
if (leave) {
|
||||
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
|
||||
history.replaceState(history.state, '', `/scripts/edit/${newHash}`)
|
||||
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
|
||||
} else {
|
||||
await goto(`/scripts/edit/${newHash}?step=2`)
|
||||
await goto(`/scripts/edit/${newHash}`)
|
||||
script.hash = newHash
|
||||
topHash = undefined
|
||||
}
|
||||
@@ -117,243 +126,231 @@
|
||||
loadingSave = false
|
||||
}
|
||||
|
||||
async function changeStep(step: number) {
|
||||
if (step > 1) {
|
||||
script.schema = script.schema ?? emptySchema()
|
||||
try {
|
||||
await inferArgs(script.language, script.content, script.schema)
|
||||
} catch (error) {
|
||||
console.info(
|
||||
'The main signature was not parsable. This script is considered to be without main function'
|
||||
)
|
||||
const dropdownItems: Array<{ label: string; onClick: () => void }> = [
|
||||
{
|
||||
label: 'Save and leave',
|
||||
onClick: () => editScript(true)
|
||||
}
|
||||
]
|
||||
|
||||
if (initialPath != '') {
|
||||
dropdownItems.push({
|
||||
label: 'Fork',
|
||||
onClick: () => {
|
||||
window.open(`/scripts/add?template=${initialPath}`)
|
||||
}
|
||||
}
|
||||
goto(`?step=${step}`)
|
||||
}
|
||||
|
||||
$: kind = script.kind as 'script' | 'trigger' | 'approval' | undefined
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (event.key == 'Enter' && step == 1) {
|
||||
changeStep(2)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<div class="flex flex-col h-screen">
|
||||
<!-- Nav between steps-->
|
||||
<div class="flex flex-col w-full px-2 py-1 border-b shadow-sm">
|
||||
<div
|
||||
class="justify-between flex flex-row w-full items-center overflow-x-auto scrollbar-hidden px-2"
|
||||
>
|
||||
<div class="flex flex-row py-1">
|
||||
<Breadcrumb
|
||||
items={['Metadata', 'Code', 'Advanced']}
|
||||
selectedIndex={step}
|
||||
on:select={(e) => changeStep(e.detail.index + 1)}
|
||||
disabled={pathError != ''}
|
||||
>
|
||||
<svelte:fragment slot="separator">/</svelte:fragment>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<Drawer placement="right" bind:open={metadataOpen} size="800px">
|
||||
<DrawerContent title="Metadata" on:close={() => (metadataOpen = false)}>
|
||||
<h2 class="border-b pb-1 mb-4">Path</h2>
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-10 mb-4">Summary</h2>
|
||||
|
||||
<div class="gap-1 flex-row hidden md:flex shrink overflow-hidden">
|
||||
<Button
|
||||
btnClasses="hidden lg:inline-flex"
|
||||
startIcon={{ icon: faPen }}
|
||||
variant="contained"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
await changeStep(1)
|
||||
setTimeout(() => pathC?.focus(), 100)
|
||||
}}
|
||||
>
|
||||
{script.path}
|
||||
</Button>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-10 mb-4">Language</h2>
|
||||
|
||||
<Button
|
||||
startIcon={{ icon: faPen }}
|
||||
variant="contained"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
await changeStep(1)
|
||||
setTimeout(() => summaryC?.focus(), 100)
|
||||
}}
|
||||
>
|
||||
<div class="max-w-[10em] !truncate">
|
||||
{script.summary == '' || !script.summary ? 'No summary' : script.summary}
|
||||
</div>
|
||||
</Button>
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-gray-600 italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
<div class="flex flex-row gap-x-2">
|
||||
{/if}
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
size="sm"
|
||||
btnClasses={step == 1 ? 'hidden sm:invisible' : ''}
|
||||
on:click={() => changeStep(step - 1)}
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
script.language = lang
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
Back
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2">{label}</span>
|
||||
</Button>
|
||||
{/each}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'pgsql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
template = 'pgsql'
|
||||
initContent(Script.language.DENO, script.kind, template)
|
||||
script.language = Script.language.DENO
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-2">PostgreSQL</span>
|
||||
</Button>
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'mysql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
|
||||
</Button> -->
|
||||
</div>
|
||||
|
||||
<h2 class="border-b pb-1 mt-10 mb-4">Description</h2>
|
||||
<textarea
|
||||
use:autosize
|
||||
bind:value={script.description}
|
||||
placeholder="Edit description"
|
||||
class="text-sm"
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="flex flex-col w-full px-2 py-1 border-b shadow-sm">
|
||||
<div class="justify-between flex gap-8 w-full items-center px-2">
|
||||
<div class="min-w-64 w-full max-w-md">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Script summary"
|
||||
class="text-sm w-full font-semibold"
|
||||
bind:value={script.summary}
|
||||
/>
|
||||
</div>
|
||||
<div class="gap-4 flex">
|
||||
<div class="flex justify-start w-full">
|
||||
<div>
|
||||
<button
|
||||
on:click={async () => {
|
||||
metadataOpen = true
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-gray-300 !text-gray-600 !h-[28px] !w-[70px] rounded-r-none"
|
||||
>
|
||||
<Pen size={12} class="mr-2" /> Path
|
||||
</Badge>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={script.path}
|
||||
size={script.path?.length || 50}
|
||||
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center-center">
|
||||
<button
|
||||
on:click={async () => {
|
||||
metadataOpen = true
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang={script.language} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-row gap-x-4">
|
||||
<Button
|
||||
color="dark"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
advancedOpen = true
|
||||
}}
|
||||
>
|
||||
Customise
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={step == 1 ? 'contained' : 'border'}
|
||||
btnClasses={step == 3 ? 'invisible' : 'inline-flex gap-2'}
|
||||
disabled={step === 1 && pathError !== ''}
|
||||
on:click={() => changeStep(step + 1)}
|
||||
>
|
||||
Next {#if step == 1}<Kbd>Enter</Kbd>{/if}
|
||||
</Button>
|
||||
<ButtonPopup
|
||||
color="dark"
|
||||
loading={loadingSave}
|
||||
size="sm"
|
||||
variant={step == 1 ? 'border' : 'contained'}
|
||||
disabled={step === 1 && pathError !== ''}
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={() => editScript(false)}
|
||||
{dropdownItems}
|
||||
>
|
||||
<svelte:fragment slot="main">Save</svelte:fragment>
|
||||
<ButtonPopupItem on:click={() => editScript(true)}>Save and exit</ButtonPopupItem>
|
||||
{#if initialPath != ''}
|
||||
<ButtonPopupItem
|
||||
on:click={() => {
|
||||
window.open(`/scripts/add?template=${initialPath}`)
|
||||
}}>Fork</ButtonPopupItem
|
||||
>
|
||||
{/if}
|
||||
</ButtonPopup>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- metadata -->
|
||||
{#if step === 1}
|
||||
<CenteredPage>
|
||||
<SettingSection title="Path" element="h2">
|
||||
<Path
|
||||
bind:this={pathC}
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
on:enter={() => changeStep(2)}
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection title="Summary" element="h2">
|
||||
<input
|
||||
type="text"
|
||||
bind:this={summaryC}
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection title="Language" element="h2">
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-gray-600 italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = lang
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2">{label}</span>
|
||||
</Button>
|
||||
{/each}
|
||||
<Drawer bind:open={advancedOpen} size="800px">
|
||||
<DrawerContent title="Customise" on:close={() => (advancedOpen = false)}>
|
||||
<h2 class="border-b pb-1 mb-4"
|
||||
>Script Kind <Tooltip
|
||||
>Tag this script's purpose within flows such that it is available as the corresponding
|
||||
action.</Tooltip
|
||||
></h2
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each scriptKindOptions as { value, title, desc }}
|
||||
{@const isPicked = script.kind === value}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'pgsql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses="font-medium {isPicked ? '!bg-blue-50/75' : ''}"
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'pgsql'
|
||||
initContent(script.language, script.kind, template)
|
||||
template = 'script'
|
||||
script.kind = value
|
||||
initContent(script.language, value, template)
|
||||
setCode(script.content)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-2">PostgreSQL</span>
|
||||
{title}
|
||||
{#if desc}
|
||||
<Tooltip class="mb-0.5 ml-1">
|
||||
{desc}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Button>
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'mysql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
|
||||
</Button> -->
|
||||
</div>
|
||||
</SettingSection>
|
||||
<SettingSection
|
||||
title="Script kind"
|
||||
element="h3"
|
||||
tooltip="Tag this script as having a specific purpose inside flows. If it won't be used in flows,
|
||||
you don't have to worry about this."
|
||||
accordion
|
||||
{/each}
|
||||
</div>
|
||||
<h2 class="border-b pb-1 mt-10 mb-4"
|
||||
>Arguments <Tooltip
|
||||
>The arguments are synced with the main signature but you may refine the parts that
|
||||
cannot be inferred from the type directly.</Tooltip
|
||||
></h2
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each scriptKindOptions as { value, title, desc }}
|
||||
{@const isPicked = script.kind === value}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses="font-medium {isPicked ? '!bg-blue-50/75' : ''}"
|
||||
on:click={() => {
|
||||
template = 'script'
|
||||
script.kind = value
|
||||
initContent(script.language, value, template)
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
{title}
|
||||
{#if desc}
|
||||
<Tooltip class="mb-0.5 ml-1">
|
||||
{desc}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
</SettingSection>
|
||||
</CenteredPage>
|
||||
{:else if step === 2}
|
||||
<ScriptEditor
|
||||
bind:schema={script.schema}
|
||||
path={script.path}
|
||||
bind:code={script.content}
|
||||
lang={script.language}
|
||||
{initialArgs}
|
||||
{kind}
|
||||
/>
|
||||
{:else if step === 3}
|
||||
<CenteredPage>
|
||||
<ScriptSchema bind:description={script.description} bind:schema={script.schema} />
|
||||
</CenteredPage>
|
||||
{/if}
|
||||
<ScriptSchema bind:schema={script.schema} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<ScriptEditor
|
||||
bind:editor
|
||||
bind:this={scriptEditor}
|
||||
bind:schema={script.schema}
|
||||
path={script.path}
|
||||
bind:code={script.content}
|
||||
lang={script.language}
|
||||
{initialArgs}
|
||||
kind={script.kind}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
Script Builder not available to operators
|
||||
{/if}
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
@@ -23,18 +23,16 @@
|
||||
export let code: string
|
||||
export let path: string | undefined
|
||||
export let lang: Preview.language
|
||||
export let kind: 'script' | 'trigger' | 'approval' | undefined = undefined
|
||||
export let kind: string | undefined = undefined
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let fixedOverflowWidgets = true
|
||||
export let noSyncFromGithub = false
|
||||
export let editor: Editor | undefined = undefined
|
||||
|
||||
let websocketAlive = { pyright: false, black: false, deno: false, go: false }
|
||||
|
||||
let width = 1200
|
||||
|
||||
// Internal state
|
||||
let editor: Editor
|
||||
|
||||
let testJobLoader: TestJobLoader
|
||||
|
||||
// Test args input
|
||||
@@ -70,7 +68,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
async function inferSchema(code: string) {
|
||||
export async function inferSchema(code: string, nlang?: 'go' | 'bash' | 'python3' | 'deno') {
|
||||
schema = schema ?? emptySchema()
|
||||
let isDefault: string[] = []
|
||||
Object.entries(args).forEach(([k, v]) => {
|
||||
@@ -80,10 +78,9 @@
|
||||
})
|
||||
|
||||
try {
|
||||
await inferArgs(lang, code, schema)
|
||||
await inferArgs(nlang ?? lang, code, schema)
|
||||
validCode = true
|
||||
} catch (e) {
|
||||
console.error("Couldn't infer args", e)
|
||||
validCode = false
|
||||
}
|
||||
|
||||
@@ -103,6 +100,10 @@
|
||||
inferSchema(code)
|
||||
loadPastTests()
|
||||
})
|
||||
|
||||
function asKind(str: string | undefined) {
|
||||
return str as 'script' | 'approval' | 'trigger' | undefined
|
||||
}
|
||||
</script>
|
||||
|
||||
<TestJobLoader
|
||||
@@ -122,7 +123,7 @@
|
||||
{editor}
|
||||
{lang}
|
||||
{websocketAlive}
|
||||
{kind}
|
||||
kind={asKind(kind)}
|
||||
/>
|
||||
{#if !noSyncFromGithub}
|
||||
<div class="py-1">
|
||||
@@ -145,37 +146,35 @@
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={60} minSize={10} class="!overflow-visible">
|
||||
<div
|
||||
class="pl-2 h-full !overflow-visible"
|
||||
on:mouseleave={() => {
|
||||
inferSchema(code)
|
||||
}}
|
||||
>
|
||||
<Editor
|
||||
bind:code
|
||||
bind:websocketAlive
|
||||
bind:this={editor}
|
||||
on:change={(e) => {
|
||||
inferSchema(e.detail)
|
||||
}}
|
||||
cmdEnterAction={async () => {
|
||||
await inferSchema(code)
|
||||
runTest()
|
||||
}}
|
||||
formatAction={async () => {
|
||||
await inferSchema(code)
|
||||
try {
|
||||
localStorage.setItem(path ?? 'last_save', code)
|
||||
} catch (e) {
|
||||
console.error('Could not save last_save to local storage', e)
|
||||
}
|
||||
lastSave = code
|
||||
}}
|
||||
class="flex flex-1 h-full !overflow-visible"
|
||||
lang={scriptLangToEditorLang(lang)}
|
||||
automaticLayout={true}
|
||||
{fixedOverflowWidgets}
|
||||
/>
|
||||
<div class="pl-2 h-full !overflow-visible">
|
||||
{#key lang}
|
||||
<Editor
|
||||
{path}
|
||||
bind:code
|
||||
bind:websocketAlive
|
||||
bind:this={editor}
|
||||
on:change={(e) => {
|
||||
inferSchema(e.detail)
|
||||
}}
|
||||
cmdEnterAction={async () => {
|
||||
await inferSchema(code)
|
||||
runTest()
|
||||
}}
|
||||
formatAction={async () => {
|
||||
await inferSchema(code)
|
||||
try {
|
||||
localStorage.setItem(path ?? 'last_save', code)
|
||||
} catch (e) {
|
||||
console.error('Could not save last_save to local storage', e)
|
||||
}
|
||||
lastSave = code
|
||||
}}
|
||||
class="flex flex-1 h-full !overflow-visible"
|
||||
lang={scriptLangToEditorLang(lang)}
|
||||
automaticLayout={true}
|
||||
{fixedOverflowWidgets}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={40} minSize={10}>
|
||||
@@ -185,9 +184,10 @@
|
||||
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red" size="xs">
|
||||
<WindmillIcon
|
||||
white={true}
|
||||
class="animate-[spin_5s_linear_infinite] mr-2 text-white"
|
||||
class="mr-2 text-white"
|
||||
height="20px"
|
||||
width="20px"
|
||||
spin="fast"
|
||||
/>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -8,11 +8,8 @@
|
||||
|
||||
import Highlight from 'svelte-highlight'
|
||||
import json from 'svelte-highlight/languages/json'
|
||||
import { Alert } from './common'
|
||||
import autosize from 'svelte-autosize'
|
||||
|
||||
export let schema: Schema
|
||||
export let description: string | undefined
|
||||
|
||||
export function setSchema(newSchema: Schema) {
|
||||
schema = newSchema
|
||||
@@ -20,28 +17,12 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<h1 class="my-4">Advanced</h1>
|
||||
|
||||
<Tabs selected="ui">
|
||||
<Tab value="ui">Description & Arguments refinement</Tab>
|
||||
<Tab value="ui">Arguments</Tab>
|
||||
<Tab value="jsonschema">JSON Schema</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="ui">
|
||||
<h2 class="border-b pb-1 mt-6 mb-4">Description</h2>
|
||||
<textarea
|
||||
use:autosize
|
||||
bind:value={description}
|
||||
placeholder="Edit description"
|
||||
class="text-sm"
|
||||
/>
|
||||
|
||||
<h2 class="border-b pb-1 mb-4 mt-8">Arguments</h2>
|
||||
<Alert type="info" title="Synchronized with main signature">
|
||||
Argument names, being required or not, and default values are derived from the main
|
||||
signature of step 2 and cannot be edited directly. Change the main signature to edit them.
|
||||
</Alert>
|
||||
<div class="mt-4" />
|
||||
|
||||
<SchemaForm {schema} editableSchema={true} />
|
||||
</TabContent>
|
||||
<TabContent value="jsonschema">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
return
|
||||
}
|
||||
// pre-filter
|
||||
let idxs = uf.filter(plaintextItems, filter)
|
||||
let idxs = uf.filter(plaintextItems, filter) ?? []
|
||||
|
||||
let info = uf.info(idxs, plaintextItems, filter)
|
||||
let order = uf.sort(info, plaintextItems, filter)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user