Compare commits

..

15 Commits

Author SHA1 Message Date
Ruben Fiszel
020e5f7b32 update metrics 2023-02-27 11:56:55 +01:00
Ruben Fiszel
1414030afe chore(main): release 1.70.1 (#1241) 2023-02-27 10:41:23 +01:00
Ruben Fiszel
92bf928b78 chore(main): release 1.70.1 (#1239)
* chore(main): release 1.70.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-27 10:41:23 +01:00
Faton Ramadani
0bcc036a84 fix(frontend): Fix inline scripts list (#1240) 2023-02-27 10:41:23 +01:00
Faton Ramadani
6277188c1b fix(frontend): Fix subgrid lock (#1232)
* fix(frontend): Fix subgrid lock

* feat(frontend): restore
2023-02-27 10:41:23 +01:00
Ruben Fiszel
65c9d43419 fix findGridItemById 2023-02-27 10:41:23 +01:00
Faton Ramadani
cc5744ee2d fix(frontend): Disable move in nested subgrid (#1238)
* fix(frontend): Disable move in nested subgrid

* fix(frontend): Disable move in nested subgrid
2023-02-27 10:41:23 +01:00
Ruben Fiszel
1e796881b3 fix(cli): make cli resilient to systems without openable browsers 2023-02-27 10:41:23 +01:00
Ruben Fiszel
a55d99a8b7 chore(main): release 1.70.0 (#1236)
* chore(main): release 1.70.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-27 10:41:23 +01:00
Ruben Fiszel
07477f9e35 fix(cli): bump cli to non broken client 1.69.3 2023-02-27 10:41:23 +01:00
Ruben Fiszel
9409d5e266 update 2023-02-27 10:41:23 +01:00
Ruben Fiszel
2d71ebbe09 fix stripe checkout 2023-02-27 10:41:23 +01:00
Ruben Fiszel
c1673ac036 update 2023-02-27 08:27:08 +01:00
Ruben Fiszel
8ca54e02c0 Merge branch 'main' into rf/diff3 2023-02-27 08:23:29 +01:00
Ruben Fiszel
53ddf013df foo 2023-02-24 11:01:45 +01:00
339 changed files with 3706 additions and 14825 deletions

View File

@@ -0,0 +1,20 @@
name: Deploy to windmill.dev
on:
push:
branches: [main]
paths:
- "community/**"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to windmill.dev
uses: windmill-labs/windmill-gh-action-deploy@v2.0.0
with:
dry_run: false
input_dir: community
windmill_workspace: starter
windmill_token: ${{ secrets.WINDMILL_API_TOKEN }}

View File

@@ -109,38 +109,38 @@ jobs:
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
# disabled until we make it 100% reliable and add more meaningful tests
# playwright:
# runs-on: [self-hosted, new]
# needs: [build]
# services:
# postgres:
# image: postgres
# env:
# POSTGRES_DB: windmill
# POSTGRES_USER: admin
# POSTGRES_PASSWORD: changeme
# ports:
# - 5432:5432
# options: >-
# --health-cmd pg_isready
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# steps:
# - uses: actions/checkout@v3
# - name: "Docker"
# run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
# id: docker-container
# - uses: actions/setup-node@v3
# with:
# node-version: 16
# - name: "Playwright run"
# timeout-minutes: 2
# run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=http://localhost:8000 && npm run test
# - name: "Clean up"
# run: docker kill ${{ steps.docker-container.outputs.id }}
# if: always()
playwright:
runs-on: [self-hosted, new]
needs: [build]
services:
postgres:
image: postgres
env:
POSTGRES_DB: windmill
POSTGRES_USER: admin
POSTGRES_PASSWORD: changeme
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: "Docker"
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
id: docker-container
- uses: actions/setup-node@v3
with:
node-version: 16
- name: "Playwright run"
timeout-minutes: 2
run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=http://localhost:8000 && npm run test
- name: "Clean up"
run: docker kill ${{ steps.docker-container.outputs.id }}
if: always()
publish_privately_heavy:
@@ -182,7 +182,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push privately
uses: docker/build-push-action@v4
uses: docker/build-push-action@v3
if: github.event_name != 'pull_request'
with:
context: .
@@ -222,7 +222,7 @@ jobs:
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Build and push privately
uses: docker/build-push-action@v4
uses: docker/build-push-action@v3
if: github.event_name != 'pull_request'
with:
context: .

View File

@@ -65,7 +65,7 @@ jobs:
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Build and push publicly
uses: docker/build-push-action@v4
uses: docker/build-push-action@v3
with:
context: "{{defaultContext}}:lsp"
push: true

View File

@@ -25,12 +25,12 @@ jobs:
run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v3
with:
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
tags: type=raw,value=60d
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v2
with:
push: true
context: ./

View File

@@ -1,108 +1,6 @@
# Changelog
## [1.74.2](https://github.com/windmill-labs/windmill/compare/v1.74.1...v1.74.2) (2023-03-09)
### Bug Fixes
* **frontend:** fix splitpanes navigation ([#1276](https://github.com/windmill-labs/windmill/issues/1276)) ([8d5c5b8](https://github.com/windmill-labs/windmill/commit/8d5c5b88a35d7a3bad1d8ddf2d940026825241eb))
## [1.74.1](https://github.com/windmill-labs/windmill/compare/v1.74.0...v1.74.1) (2023-03-09)
### Bug Fixes
* **apps:** proper reactivity for non rendered static components ([ae53baf](https://github.com/windmill-labs/windmill/commit/ae53bafaf6777f928113f84b2c6ed6a2ed341844))
* **ci:** make windmill compile again by pinning swc deps ([2ea15d5](https://github.com/windmill-labs/windmill/commit/2ea15d5035e5e15473968db3c0501a4dddff5cd0))
## [1.74.0](https://github.com/windmill-labs/windmill/compare/v1.73.1...v1.74.0) (2023-03-09)
### Features
* add delete by path for scripts ([0c2cf92](https://github.com/windmill-labs/windmill/commit/0c2cf92dd3df9610e649f15e23921a4ca0d94e6a))
* **frontend:** Add color picker input to app ([#1270](https://github.com/windmill-labs/windmill/issues/1270)) ([88e537a](https://github.com/windmill-labs/windmill/commit/88e537ad1fb4c207f38fbe951c82106bef6491a3))
* **frontend:** add expand ([#1268](https://github.com/windmill-labs/windmill/issues/1268)) ([b854ee3](https://github.com/windmill-labs/windmill/commit/b854ee34393534bde104e2e6f606108fd66d38dc))
* **frontend:** add hash to ctx in apps ([b1a45b1](https://github.com/windmill-labs/windmill/commit/b1a45b1e708aa6f19f8be9c949507083e044f2d8))
* **frontend:** Add key navigation in app editor ([#1273](https://github.com/windmill-labs/windmill/issues/1273)) ([6b0fb75](https://github.com/windmill-labs/windmill/commit/6b0fb75d23e2151c88b07814139d203c1bd0578d))
### Bug Fixes
* **cli:** improve visibility of the active workspace ([e6344da](https://github.com/windmill-labs/windmill/commit/e6344dac6d1be04b46231fa8ef8579fd12ca8f37))
* **frontend:** add confirmation modal to delete script/flow/app ([a4adcb5](https://github.com/windmill-labs/windmill/commit/a4adcb5192c11f7bf47a0d259825e474779378d7))
* **frontend:** Clean up app editor ([#1267](https://github.com/windmill-labs/windmill/issues/1267)) ([0a5e181](https://github.com/windmill-labs/windmill/commit/0a5e181a3aa966fb8211bee0d9174fc16353b31f))
* **frontend:** Minor changes ([#1272](https://github.com/windmill-labs/windmill/issues/1272)) ([3b6ae0c](https://github.com/windmill-labs/windmill/commit/3b6ae0cc49461b858d9cfff79eae9a7569465235))
* **frontend:** simplify input bindings ([b2de531](https://github.com/windmill-labs/windmill/commit/b2de531a46e4b120d7106d361b727746bec516dd))
## [1.73.1](https://github.com/windmill-labs/windmill/compare/v1.73.0...v1.73.1) (2023-03-07)
### Bug Fixes
* **frontend:** load flow is not initialized ([719d475](https://github.com/windmill-labs/windmill/commit/719d4752621d462b1cfaa0d27930fba7586be779))
## [1.73.0](https://github.com/windmill-labs/windmill/compare/v1.72.0...v1.73.0) (2023-03-07)
### Features
* **frontend:** add a way to automatically resize ([#1259](https://github.com/windmill-labs/windmill/issues/1259)) ([24f58ef](https://github.com/windmill-labs/windmill/commit/24f58efd9994a2201c1b1d9bbfb11734c57068e3))
* **frontend:** add ability to move nodes ([614fb50](https://github.com/windmill-labs/windmill/commit/614fb5022aa7d5428fb96b7ee3a20794edd1e9d3))
* **frontend:** Add app PDF viewer ([#1254](https://github.com/windmill-labs/windmill/issues/1254)) ([3e5d09e](https://github.com/windmill-labs/windmill/commit/3e5d09ef0b5619186bee5ec6d442cbfd12a6e8d5))
* **frontend:** add fork/save buttons + consistent styling for slider/range ([9e9f8ef](https://github.com/windmill-labs/windmill/commit/9e9f8efb8ee389ea75e99b67ef720756959ca737))
* **frontend:** add history to flows and apps ([9e4d90a](https://github.com/windmill-labs/windmill/commit/9e4d90ad37a57ff1f515eea0c82cf603649e915d))
* **frontend:** Fix object viewer style ([#1255](https://github.com/windmill-labs/windmill/issues/1255)) ([94f1aad](https://github.com/windmill-labs/windmill/commit/94f1aadef2b09ac1962478f11b27cc708b8328f1))
* **frontend:** refactor entire flow builder UX ([2ac51b0](https://github.com/windmill-labs/windmill/commit/2ac51b0af08bdef7ce3c7e874e9983b9fc00478a))
### Bug Fixes
* **frontend:** arginput + apppreview fixes ([e2c4545](https://github.com/windmill-labs/windmill/commit/e2c45452401022b00285b21551ffaf35a114be33))
* **frontend:** fix app map reactivity ([#1260](https://github.com/windmill-labs/windmill/issues/1260)) ([2557e13](https://github.com/windmill-labs/windmill/commit/2557e136bd0df1a023819b7d9b2235e30d7140b6))
* **frontend:** fix branch deletion ([#1261](https://github.com/windmill-labs/windmill/issues/1261)) ([a999eb2](https://github.com/windmill-labs/windmill/commit/a999eb21121a7c0010621448324e0c77caf2b3f6))
* **frontend:** Side menu z-index issue ([#1265](https://github.com/windmill-labs/windmill/issues/1265)) ([c638897](https://github.com/windmill-labs/windmill/commit/c638897fdcd58f55b0929f91641b21a6f9d25ead))
## [1.72.0](https://github.com/windmill-labs/windmill/compare/v1.71.0...v1.72.0) (2023-03-02)
### Features
* **backend:** get_result_by_id do a downward pass to find node at any depth ([#1249](https://github.com/windmill-labs/windmill/issues/1249)) ([4c913dc](https://github.com/windmill-labs/windmill/commit/4c913dc4b6be03571a015c97a13829adffb61479))
* **frontend:** Add app map component ([#1251](https://github.com/windmill-labs/windmill/issues/1251)) ([ed25d9f](https://github.com/windmill-labs/windmill/commit/ed25d9f186d9925f75404cb193a025d8a41c4540))
* **frontend:** app splitpanes ([#1248](https://github.com/windmill-labs/windmill/issues/1248)) ([f4d79ee](https://github.com/windmill-labs/windmill/commit/f4d79ee2633e6cdab0fa2410108b31cfa77e10da))
### Bug Fixes
* **backend:** improve result retrieval ([c4463bb](https://github.com/windmill-labs/windmill/commit/c4463bb029907f3c8d77abb194f872aae7876bf6))
* **backend:** incorrect get_result_by_id for list_result job ([2a75cd2](https://github.com/windmill-labs/windmill/commit/2a75cd250ea5e01849fc8bbb69bf44f147d0acb8))
* **cli:** fix workspace option + run script/flow + whoami ([35ea2b2](https://github.com/windmill-labs/windmill/commit/35ea2b27b12159c68c8507ec1f8686028c975387))
* **frontend:** background script not showing inputs ([55eb48c](https://github.com/windmill-labs/windmill/commit/55eb48c55332431304cedbf3bcbbbcff61ec3645))
* **frontend:** fix table bindings ([2679386](https://github.com/windmill-labs/windmill/commit/2679386bf87a56352269911bd89e52df5ee9f314))
* **frontend:** rework app reactivity ([94b20d2](https://github.com/windmill-labs/windmill/commit/94b20d2f5e3b551974c57ea82b6e3dc16e97b9b8))
* **frontend:** rework app reactivity ([1753cb7](https://github.com/windmill-labs/windmill/commit/1753cb7da658f47be974c15da82c71a8e19309a6))
## [1.71.0](https://github.com/windmill-labs/windmill/compare/v1.70.1...v1.71.0) (2023-02-28)
### Features
* **backend:** use counter for sleep/execution/pull durations ([e568690](https://github.com/windmill-labs/windmill/commit/e56869092a03fec4703ddd9ef65c89edb8122962))
* **cli:** add autocompletions ([287b2db](https://github.com/windmill-labs/windmill/commit/287b2db22f7b56e90bcd0c4727c00096695c2e0d))
* **frontend:** App drawer ([#1246](https://github.com/windmill-labs/windmill/issues/1246)) ([8a0d115](https://github.com/windmill-labs/windmill/commit/8a0d1158c4d7e970cb91e1adf4838e5efdbb39ff))
* **frontend:** drawer for editing workspace scripts in flows ([6adc875](https://github.com/windmill-labs/windmill/commit/6adc87561070d8aceaba1838008cd7e6be2e2660))
### Bug Fixes
* **frontend:** Add more app custom css ([#1229](https://github.com/windmill-labs/windmill/issues/1229)) ([a4e4d18](https://github.com/windmill-labs/windmill/commit/a4e4d188ad10443dd0b7f104389594efc768dc59))
* **frontend:** Add more app custom css ([#1247](https://github.com/windmill-labs/windmill/issues/1247)) ([1bb5ed9](https://github.com/windmill-labs/windmill/commit/1bb5ed9ae01fd7998b06833b6222e5dd5d774d35))
* **frontend:** display currently selected filter even if not in list ([42d1cd6](https://github.com/windmill-labs/windmill/commit/42d1cd6456620ba917c560c87d736dc93634adff))
* **frontend:** Fix deeply nested move ([#1245](https://github.com/windmill-labs/windmill/issues/1245)) ([a67f10e](https://github.com/windmill-labs/windmill/commit/a67f10eeb6fdb44bbb3a510badcc5ad0ae187a2b))
* **frontend:** invisible subgrids have h-0 + app policies fix ([2244e83](https://github.com/windmill-labs/windmill/commit/2244e83b9da803a4cf46ab0825d7cb6cb0e24872))
## [1.70.1](https://github.com/windmill-labs/windmill/compare/v1.70.0...v1.70.1) (2023-02-27)

View File

@@ -73,7 +73,7 @@ ARG features=""
COPY --from=planner /windmill/recipe.json recipe.json
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef cook --release --features "$features" --recipe-path recipe.json
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
COPY ./backend ./
@@ -86,7 +86,6 @@ RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$feature
FROM python:3.11.2-slim-buster
ARG TARGETPLATFORM
ARG APP=/usr/src/app
@@ -130,10 +129,6 @@ COPY --from=nsjail /nsjail/nsjail /bin/nsjail
COPY --from=denoland/deno:latest /usr/bin/deno /usr/bin/deno
# docker does not support conditional COPY and we want to use the same Dockerfile for both amd64 and arm64 and privilege the official image
COPY --from=lukechannings/deno:latest /usr/bin/deno /usr/bin/deno-arm
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then rm /usr/bin/deno-arm; elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then mv /usr/bin/deno-arm /usr/bin/deno; fi
RUN mkdir -p ${APP}
WORKDIR ${APP}

View File

@@ -8,9 +8,5 @@ or belonging to one of the below cases:
The files under backend/ are AGPL Licensed.
The files under frontend/ are AGPL Licensed.
The files under python-client/ deno-client/ go-client/ are Apache 2.0 Licensed.
The openapi files, including the OpenFlow spec is Apache 2.0 Licensed.
All third party components incorporated into the Windmill Software are licensed under the
original license provided by the owner of the applicable component.
The files under python-client/ are Apache 2.0 Licensed.
The files under community/ are Apache 2.0 Licensed.

View File

@@ -284,7 +284,6 @@ you to have it being synced automatically everyday.
| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| DATABASE_URL | | The Postgres database url. | All |
| DISABLE_NSJAIL | true | Disable Nsjail Sandboxing | Worker |
| SERVER_BIND_ADDR | 0.0.0.0 | IP Address on which to bind listening socket | Server |
| PORT | 8000 | Exposed port | Server | |
| NUM_WORKERS | 3 | The number of worker per Worker instance (set to 1 on Eks to have 1 pod = 1 worker, set to 0 for an API only instance) | Worker |
| DISABLE_SERVER | false | Binary would operate as a worker only instance | Worker |

350
backend/Cargo.lock generated
View File

@@ -67,9 +67,9 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "argon2"
version = "0.5.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
@@ -129,11 +129,12 @@ dependencies = [
[[package]]
name = "async-lock"
version = "2.7.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
dependencies = [
"event-listener",
"futures-lite",
]
[[package]]
@@ -202,9 +203,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.66"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
dependencies = [
"proc-macro2",
"quote",
@@ -254,9 +255,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.7"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
checksum = "6137c6234afb339e75e764c866e3594900f0211e1315d33779f269bbe2ec6967"
dependencies = [
"async-trait",
"axum-core",
@@ -281,16 +282,16 @@ dependencies = [
"sync_wrapper",
"tokio",
"tower",
"tower-http 0.3.5",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
dependencies = [
"async-trait",
"bytes",
@@ -336,6 +337,15 @@ dependencies = [
"scoped-tls",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
@@ -378,9 +388,9 @@ dependencies = [
[[package]]
name = "block-buffer"
version = "0.10.4"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
@@ -515,9 +525,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.1.8"
version = "4.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
dependencies = [
"bitflags",
"clap_derive",
@@ -530,9 +540,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.1.8"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
@@ -694,9 +704,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.15"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
@@ -719,9 +729,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.92"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -731,9 +741,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.92"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
dependencies = [
"cc",
"codespan-reporting",
@@ -746,15 +756,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.92"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
[[package]]
name = "cxxbridge-macro"
version = "1.0.92"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
dependencies = [
"proc-macro2",
"quote",
@@ -895,7 +905,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.4",
"block-buffer 0.10.3",
"const-oid",
"crypto-common",
"subtle",
@@ -956,9 +966,9 @@ checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
[[package]]
name = "dyn-clone"
version = "1.0.11"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
[[package]]
name = "dyn-iter"
@@ -1314,9 +1324,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.16"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
dependencies = [
"bytes",
"fnv",
@@ -1612,9 +1622,9 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.6"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
dependencies = [
"libc",
"windows-sys 0.45.0",
@@ -1662,15 +1672,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.6"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "jobserver"
version = "0.1.26"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
dependencies = [
"libc",
]
@@ -1815,9 +1825,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.140"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libgit2-sys"
@@ -2038,6 +2048,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -2084,6 +2103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
@@ -2127,6 +2147,27 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@@ -2263,9 +2304,9 @@ dependencies = [
[[package]]
name = "password-hash"
version = "0.5.0"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
@@ -2274,9 +2315,9 @@ dependencies = [
[[package]]
name = "paste"
version = "1.0.12"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]]
name = "pem-rfc7468"
@@ -2295,9 +2336,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
version = "2.5.6"
version = "2.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7"
checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660"
dependencies = [
"thiserror",
"ucd-trie",
@@ -2475,9 +2516,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
dependencies = [
"once_cell",
"toml_edit",
@@ -2525,7 +2566,7 @@ dependencies = [
[[package]]
name = "progenitor"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
dependencies = [
"anyhow",
"built",
@@ -2543,7 +2584,7 @@ dependencies = [
[[package]]
name = "progenitor-client"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
dependencies = [
"bytes",
"futures-core",
@@ -2557,7 +2598,7 @@ dependencies = [
[[package]]
name = "progenitor-impl"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
dependencies = [
"getopts",
"heck",
@@ -2579,7 +2620,7 @@ dependencies = [
[[package]]
name = "progenitor-macro"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
dependencies = [
"openapiv3",
"proc-macro2",
@@ -2846,9 +2887,9 @@ dependencies = [
[[package]]
name = "rust-embed"
version = "6.6.0"
version = "6.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb133b9a38b5543fad3807fb2028ea47c5f2b566f4f5e28a11902f1a358348b6"
checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -2857,9 +2898,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "6.5.0"
version = "6.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
dependencies = [
"proc-macro2",
"quote",
@@ -2870,9 +2911,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
version = "7.5.0"
version = "7.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
dependencies = [
"sha2 0.10.6",
"walkdir",
@@ -2917,9 +2958,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.36.9"
version = "0.36.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
dependencies = [
"bitflags",
"errno",
@@ -2953,7 +2994,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2962,20 +3003,24 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
dependencies = [
"bincode",
"bitflags",
"bstr",
"itertools",
"lz4_flex",
"num-bigint",
"num-complex",
"num_enum",
"serde",
"thiserror",
]
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
dependencies = [
"ahash",
"anyhow",
@@ -2990,6 +3035,7 @@ dependencies = [
"rustc-hash",
"rustpython-ast",
"rustpython-compiler-core",
"thiserror",
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
@@ -2998,15 +3044,15 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.12"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "ryu"
version = "1.0.13"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "same-file"
@@ -3066,9 +3112,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.5"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "sct"
@@ -3147,9 +3193,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.154"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
@@ -3176,9 +3222,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.154"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
@@ -3198,9 +3244,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.94"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"indexmap",
"itoa",
@@ -3210,9 +3256,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
version = "0.1.10"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0969fff533976baadd92e08b1d102c5a3d8a8049eadfd69d4d1e3c5b2ed189"
checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341"
dependencies = [
"serde",
]
@@ -3241,9 +3287,9 @@ dependencies = [
[[package]]
name = "serde_tokenstream"
version = "0.1.7"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "797ba1d80299b264f3aac68ab5d12e5825a561749db4df7cd7c8083900c5d4e9"
checksum = "274f512d6748a01e67cbcde5b4307ab2c9d52a98a2b870a980ef0793a351deff"
dependencies = [
"proc-macro2",
"serde",
@@ -3278,9 +3324,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.19"
version = "0.9.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10"
checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567"
dependencies = [
"indexmap",
"itoa",
@@ -3384,17 +3430,6 @@ dependencies = [
"syn",
]
[[package]]
name = "smartstring"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
dependencies = [
"autocfg",
"static_assertions",
"version_check",
]
[[package]]
name = "smol_str"
version = "0.1.24"
@@ -3406,9 +3441,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.4.9"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi",
@@ -3592,9 +3627,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.7"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
dependencies = [
"new_debug_unreachable",
"once_cell",
@@ -3653,9 +3688,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swc_atoms"
version = "0.4.39"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ebef84c2948cd0d1ba25acbf1b4bd9d80ab6f057efdbe35d8449b8d54699401"
checksum = "f88175a66f5a7c189e752bda520e148317776ecb22c75adc2c2f24c490834bd0"
dependencies = [
"once_cell",
"rustc-hash",
@@ -3667,9 +3702,9 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.29.35"
version = "0.29.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d515be281f603cb97afaa89896aca5e5c748fde11c7f926e35cdaa8ff8da705"
checksum = "1fc8e0e8109b26be70c82d9709562fc88cbcc09e03c2458221cf216c0088dea2"
dependencies = [
"ahash",
"ast_node",
@@ -3694,9 +3729,9 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
version = "0.98.2"
version = "0.96.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305d2aa5e36a775e0d376552b35b325fccf0d7d8c633da1e4aec8e16460251cf"
checksum = "621c66e27fbb6cbb6434a4e2b25e439e9a2583cc3419a4a83eba51d16ac0cd7b"
dependencies = [
"bitflags",
"is-macro",
@@ -3711,9 +3746,9 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.128.4"
version = "0.124.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e349e3f4c5561645b9042caae162dbaf55502be7b583ac99f3ccf3e65bccb7"
checksum = "89b3f472b3dfbfd279de364d2b014459a281824b938e243a8739037c445d6b6c"
dependencies = [
"either",
"enum_kind",
@@ -3721,7 +3756,6 @@ dependencies = [
"num-bigint",
"serde",
"smallvec",
"smartstring",
"stacker",
"swc_atoms",
"swc_common",
@@ -3830,18 +3864,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.39"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.39"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
@@ -3933,9 +3967,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.26.0"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg",
"bytes",
@@ -3949,7 +3983,7 @@ dependencies = [
"socket2",
"tokio-macros",
"tracing",
"windows-sys 0.45.0",
"windows-sys 0.42.0",
]
[[package]]
@@ -4046,19 +4080,19 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
[[package]]
name = "toml_edit"
version = "0.19.4"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
dependencies = [
"indexmap",
"nom8",
"toml_datetime",
"winnow",
]
[[package]]
@@ -4107,25 +4141,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.4.0"
@@ -4140,6 +4155,7 @@ dependencies = [
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
"tracing",
@@ -4274,7 +4290,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typify"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
dependencies = [
"typify-impl",
"typify-macro",
@@ -4283,7 +4299,7 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
dependencies = [
"heck",
"log",
@@ -4301,7 +4317,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
dependencies = [
"proc-macro2",
"quote",
@@ -4392,9 +4408,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.11"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-general-category"
@@ -4410,9 +4426,9 @@ checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a"
[[package]]
name = "unicode-ident"
version = "1.0.8"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
@@ -4449,17 +4465,15 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unicode_names2"
version = "0.6.0"
source = "git+https://github.com/youknowone/unicode_names2.git?rev=4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
dependencies = [
"phf",
]
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029df4cc8238cefc911704ff8fa210853a0f3bce2694d8f51181dd41ee0f3301"
[[package]]
name = "unsafe-libyaml"
version = "0.2.7"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c"
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
[[package]]
name = "untrusted"
@@ -4742,7 +4756,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"axum",
@@ -4769,7 +4783,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"argon2",
@@ -4808,7 +4822,7 @@ dependencies = [
"tokio-util",
"tower",
"tower-cookies",
"tower-http 0.4.0",
"tower-http",
"tracing",
"tracing-subscriber",
"urlencoding",
@@ -4824,7 +4838,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"base64 0.21.0",
"chrono",
@@ -4839,7 +4853,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"chrono",
"serde",
@@ -4852,7 +4866,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"axum",
@@ -4877,7 +4891,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"serde",
"serde_json",
@@ -4885,14 +4899,13 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"itertools",
"lazy_static",
"phf",
"regex",
"serde_json",
"unicode-general-category",
"windmill-common",
"windmill-parser",
@@ -4900,7 +4913,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"itertools",
@@ -4912,7 +4925,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"itertools",
@@ -4927,7 +4940,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"deno_core",
@@ -4941,7 +4954,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"chrono",
@@ -4964,7 +4977,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.74.2"
version = "1.70.1"
dependencies = [
"anyhow",
"async-recursion",
@@ -5078,15 +5091,6 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.74.2"
version = "1.70.1"
authors.workspace = true
edition.workspace = true
@@ -19,7 +19,7 @@ members = [
]
[workspace.package]
version = "1.74.2"
version = "1.70.1"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -123,8 +123,8 @@ regex = "^1"
deno_core = "^0"
async-recursion = "^1"
swc_common = "^0"
swc_ecma_parser = "0.128.2"
swc_ecma_ast = "0.98.1"
swc_ecma_parser = "^0"
swc_ecma_ast = "^0"
base64 = "0.21.0"
unicode-general-category = "^0"
hmac = "0.12.1"

View File

@@ -1165,3 +1165,18 @@ VALUES
}') RETURNING id)
UPDATE app SET versions = ARRAY((select id from _insert)), policy = '{ "execution_mode": "viewer", "triggerables": {} }'
WHERE workspace_id = 'admins' AND path = 'g/all/setup_app';
UPDATE script SET content = 'import wmill from "https://deno.land/x/wmill@v1.69.3/main.ts";
export async function main() {
await run(
"workspace", "add", "__automation", "admins", Deno.env.get("BASE_INTERNAL_URL") + "/", "--token", Deno.env.get("WM_TOKEN"));
await run("hub", "pull");
}
async function run(...cmd: string[]) {
console.log("Running \"" + cmd.join('' '') + "\"");
await wmill.parse(cmd);
}', summary = 'Synchronize Hub Resource types with admins workspace',
description = 'Basic administrative script to sync latest resource types from hub to share to every workspace. Recommended to run at least once. On a schedule by default.'
WHERE hash = -28028598712388162 AND workspace_id = 'admins';

View File

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

View File

@@ -1,16 +0,0 @@
-- Add up migration script here
UPDATE script SET content = 'import wmill from "https://deno.land/x/wmill@v1.70.1/main.ts";
export async function main() {
await run(
"workspace", "add", "__automation", "admins", Deno.env.get("BASE_INTERNAL_URL") + "/", "--token", Deno.env.get("WM_TOKEN"));
await run("hub", "pull");
}
async function run(...cmd: string[]) {
console.log("Running \"" + cmd.join('' '') + "\"");
await wmill.parse(cmd);
}', summary = 'Synchronize Hub Resource types with admins workspace',
description = 'Basic administrative script to sync latest resource types from hub to share to every workspace. Recommended to run at least once. On a schedule by default.'
WHERE hash = -28028598712388162 AND workspace_id = 'admins';

View File

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

View File

@@ -1,3 +0,0 @@
-- Add up migration script here
ALTER TABLE queue ADD COLUMN root_job uuid;
ALTER TABLE queue ADD COLUMN leaf_jobs jsonb;

View File

@@ -16,5 +16,4 @@ unicode-general-category.workspace = true
itertools.workspace = true
anyhow.workspace = true
regex.workspace = true
lazy_static.workspace = true
serde_json.workspace = true
lazy_static.workspace = true

View File

@@ -1,8 +1,6 @@
#![allow(non_snake_case)] // TODO: switch to parse_* function naming
use anyhow::anyhow;
use regex::Regex;
use serde_json::json;
use std::collections::HashMap;
use windmill_parser::{Arg, MainArgSignature, Typ};
@@ -19,32 +17,19 @@ pub fn parse_bash_sig(code: &str) -> windmill_common::error::Result<MainArgSigna
}
}
lazy_static::lazy_static! {
static ref RE: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+):-(.*)\})"$"#).unwrap();
}
fn parse_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut hm: HashMap<i32, (String, Option<String>)> = HashMap::new();
for cap in RE.captures_iter(code) {
hm.insert(
cap.get(2)
.or(cap.get(3))
.and_then(|x| x.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Impossible to parse arg digit"))?,
(
cap[1].to_string(),
cap.get(4).map(|x| x.as_str().to_string()),
),
);
let mut hm = HashMap::new();
let re = Regex::new(r#"(?m)^(\w+)="\$(\d+)"$"#).unwrap();
for cap in re.captures_iter(code) {
hm.insert(cap[2].parse::<i32>()?, cap[1].to_string());
}
let mut args = vec![];
for i in 1..20 {
if hm.contains_key(&i) {
let (name, default) = hm.get(&i).unwrap();
args.push(Arg {
name: name.clone(),
name: hm[&i].clone(),
typ: Typ::Str(None),
default: default.clone().map(|x| json!(x)),
default: None,
otyp: None,
has_default: false,
});
@@ -58,8 +43,6 @@ fn parse_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
@@ -67,7 +50,8 @@ mod tests {
let code = r#"
token="$1"
image="$2"
digest="${3:-latest with spaces}"
digest="${3:-latest}"
foo="$4"
"#;
//println!("{}", serde_json::to_string()?);
@@ -90,13 +74,6 @@ digest="${3:-latest with spaces}"
typ: Typ::Str(None),
default: None,
has_default: false
},
Arg {
otyp: None,
name: "digest".to_string(),
typ: Typ::Str(None),
default: Some(json!("latest with spaces")),
has_default: false
}
]
}

View File

@@ -468,27 +468,6 @@
},
"query": "DELETE FROM workspace_settings WHERE workspace_id = $1"
},
"0e7d95f4913e5775651971d741a3b5c1ef5dfe079be5325abe2866d39a7fe5fb": {
"describe": {
"columns": [
{
"name": "path",
"ordinal": 0,
"type_info": "Varchar"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Text",
"Text"
]
}
},
"query": "DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path"
},
"11b1586acdfc180c5a077861ee1f7201fcbcec9d0ebada464f9d952c9c3e400d": {
"describe": {
"columns": [],
@@ -571,6 +550,21 @@
},
"query": "SELECT * FROM workspace LIMIT $1 OFFSET $2"
},
"15de975d9be141c9ed9647935a508492aabbbddbf986d5c5c0f0c415293c432d": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
}
},
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description)\n VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'), \n ($3, 'g/all/not_secret', $4, false, 'This item is not secret')"
},
"163f00eb8b1a489d5f382cdba22a5744e88a8e6f1532d7cb02af560f5f5d49f7": {
"describe": {
"columns": [
@@ -826,6 +820,75 @@
},
"query": "\n SELECT id, flow_status, suspend, script_path\n FROM queue\n WHERE id = $1\n "
},
"1e35c39bc786d638252e5483ca4efae9a041f7e845341f8bfd715ddd9e899499": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Uuid"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Bool",
"Uuid",
"Varchar",
"Varchar",
"Timestamptz",
"Int8",
"Varchar",
"Text",
"Text",
"Jsonb",
{
"Custom": {
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies"
]
},
"name": "job_kind"
}
},
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
{
"Custom": {
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash"
]
},
"name": "script_lang"
}
},
"Bool",
"Text",
"Varchar",
"Bool"
]
}
},
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22) RETURNING id"
},
"1eaf8d677d520c7f2f303a731de6b6d939918e41ad0d1c748d80db3fd33cb9d3": {
"describe": {
"columns": [],
@@ -1107,20 +1170,6 @@
},
"query": "SELECT set_config('session.folders_read', $1, true)"
},
"2a3ebe1b0eae5b2164894321e138cc4dc0293788aeb98d05d95d18dfc708d6a6": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Text",
"Jsonb",
"Uuid"
]
}
},
"query": "\n UPDATE queue\n SET leaf_jobs = JSONB_SET(coalesce(leaf_jobs, '{}'::jsonb), ARRAY[$1::TEXT], $2)\n WHERE COALESCE((SELECT root_job FROM queue WHERE id = $3), $3) = id\n "
},
"2a4be8334db7d39f3d954193a8b0169cc4a4a07e081d2fa61d8764879d6a8ff5": {
"describe": {
"columns": [],
@@ -1134,6 +1183,33 @@
},
"query": "UPDATE script SET archived = true WHERE hash = $1 AND workspace_id = $2"
},
"2be0cfd075df9624ccbcbe5fd645e0a5c25460c2d01493f86dcdd9b2b71f6181": {
"describe": {
"columns": [
{
"name": "flow_status",
"ordinal": 0,
"type_info": "Jsonb"
},
{
"name": "parent_job",
"ordinal": 1,
"type_info": "Uuid"
}
],
"nullable": [
null,
null
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT flow_status, parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT flow_status, parent_job FROM queue WHERE id = $1 AND workspace_id = $2 "
},
"2e4115bb2e6c8c85ad1492ad135d6b0454b342126cb5fa17e58caf71b32ee755": {
"describe": {
"columns": [],
@@ -3927,76 +4003,6 @@
},
"query": "SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND workspace_id = $2)"
},
"a1c41bbeb2d64fa1e7dfd2ed053191a1de5d786ae8c22e225e450865ecac94e9": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Uuid"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Varchar",
"Uuid",
"Bool",
"Uuid",
"Varchar",
"Varchar",
"Timestamptz",
"Int8",
"Varchar",
"Text",
"Text",
"Jsonb",
{
"Custom": {
"kind": {
"Enum": [
"script",
"preview",
"flow",
"dependencies",
"flowpreview",
"script_hub",
"identity",
"flowdependencies"
]
},
"name": "job_kind"
}
},
"Varchar",
"Jsonb",
"Jsonb",
"Bool",
{
"Custom": {
"kind": {
"Enum": [
"python3",
"deno",
"go",
"bash"
]
},
"name": "script_lang"
}
},
"Bool",
"Text",
"Varchar",
"Bool",
"Uuid"
]
}
},
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23) RETURNING id"
},
"a227548b6604c56bfc15eb780bd8ee72a89dc6701a50f5048e928bd87baa7b9a": {
"describe": {
"columns": [
@@ -4107,28 +4113,6 @@
},
"query": "UPDATE flow SET dependency_job = $1 WHERE path = $2 AND workspace_id = $3"
},
"a5f9fb82791103e2bbaf9cb6d87e8c50495d12d87f8ed83382068203a8dd7a67": {
"describe": {
"columns": [
{
"name": "?column?",
"ordinal": 0,
"type_info": "Jsonb"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text",
"Uuid",
"Text"
]
}
},
"query": "SELECT leaf_jobs->$1::text FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3"
},
"a6145b0482c9e5da245059a80b1563cad20318fd2dd8aef33f9ca97de1826b8b": {
"describe": {
"columns": [],
@@ -4885,6 +4869,27 @@
},
"query": "SELECT result FROM completed_job WHERE id = $1"
},
"c2d0e44faab6981a21ca28dfd6f4eef9dfcafb471852e701c0bbc8ae11344325": {
"describe": {
"columns": [
{
"name": "parent_job",
"ordinal": 0,
"type_info": "Uuid"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT parent_job FROM queue WHERE id = $1 AND workspace_id = $2"
},
"c2d6cb56c1dea4498e2aab9ea9301dbbaa127602a38f57f5add4108fdc209b1a": {
"describe": {
"columns": [
@@ -5040,18 +5045,6 @@
},
"query": "\n SELECT id, flow_status, suspend, script_path\n FROM queue\n WHERE id = ( SELECT parent_job FROM queue WHERE id = $1 UNION ALL SELECT parent_job FROM completed_job WHERE id = $1)\n FOR UPDATE\n "
},
"c9d97800eb0ec87df8e8959b283dacb2c6cce422365ed394375641488ceb6b65": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "UPDATE worker_ping SET ping_at = now() WHERE worker = $1"
},
"cac594031a21b4806de9c4616317d3541522ef9712a83ecff7bd8b5f6e870748": {
"describe": {
"columns": [],

View File

@@ -6,7 +6,7 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::net::SocketAddr;
use git_version::git_version;
use sqlx::{Pool, Postgres};
@@ -15,7 +15,6 @@ use windmill_common::utils::rd_string;
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
const DEFAULT_NUM_WORKERS: usize = 3;
const DEFAULT_PORT: u16 = 8000;
const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
mod ee;
@@ -40,11 +39,6 @@ async fn main() -> anyhow::Result<()> {
.transpose()?
.flatten();
let server_bind_address: IpAddr = std::env::var("SERVER_BIND_ADDR")
.ok()
.and_then(|x| x.parse().ok() )
.unwrap_or(IpAddr::from(DEFAULT_SERVER_BIND_ADDR));
let port: u16 = std::env::var("PORT")
.ok()
.and_then(|x| x.parse::<u16>().ok())
@@ -66,63 +60,8 @@ async fn main() -> anyhow::Result<()> {
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
let shutdown_signal = windmill_common::shutdown_signal(tx);
#[cfg(feature = "enterprise")]
tracing::info!(
"
##############################
Windmill Enterprise Edition {GIT_VERSION}
##############################"
);
#[cfg(not(feature = "enterprise"))]
tracing::info!(
"
##############################
Windmill Community Edition {GIT_VERSION}
##############################"
);
display_config(vec![
"DISABLE_NSJAIL",
"DISABLE_SERVER",
"NUM_WORKERS",
"METRICS_ADDR",
"JSON_FMT",
"BASE_URL",
"BASE_INTERNAL_URL",
"TIMEOUT",
"SLEEP_QUEUE",
"MAX_LOG_SIZE",
"SERVER_BIND_ADDR",
"PORT",
"KEEP_JOB_DIR",
"S3_CACHE_BUCKET",
"TAR_CACHE_RATE",
"COOKIE_DOMAIN",
"PYTHON_PATH",
"DENO_PATH",
"GO_PATH",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_TRUSTED_HOST",
"PATH",
"HOME",
"DATABASE_CONNECTIONS",
"TIMEOUT_WAIT_RESULT",
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",
"WHITELIST_WORKSPACES",
"BLACKLIST_WORKSPACES",
"NEW_USER_WEBHOOK",
"CLOUD_HOSTED",
]);
if server_mode || num_workers > 0 {
let addr = SocketAddr::from((server_bind_address, port));
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let server_f = async {
if server_mode {
@@ -133,6 +72,60 @@ Windmill Community Edition {GIT_VERSION}
let workers_f = async {
if num_workers > 0 {
#[cfg(feature = "enterprise")]
tracing::info!(
"
##############################
Windmill Enterprise Edition {GIT_VERSION}
##############################"
);
#[cfg(not(feature = "enterprise"))]
tracing::info!(
"
##############################
Windmill Community Edition {GIT_VERSION}
##############################"
);
display_config(vec![
"DISABLE_NSJAIL",
"DISABLE_SERVER",
"NUM_WORKERS",
"METRICS_ADDR",
"JSON_FMT",
"BASE_URL",
"BASE_INTERNAL_URL",
"TIMEOUT",
"SLEEP_QUEUE",
"MAX_LOG_SIZE",
"PORT",
"KEEP_JOB_DIR",
"S3_CACHE_BUCKET",
"TAR_CACHE_RATE",
"COOKIE_DOMAIN",
"PYTHON_PATH",
"DENO_PATH",
"GO_PATH",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_TRUSTED_HOST",
"PATH",
"HOME",
"DATABASE_CONNECTIONS",
"TIMEOUT_WAIT_RESULT",
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",
"WHITELIST_WORKSPACES",
"BLACKLIST_WORKSPACES",
"NEW_USER_WEBHOOK",
"CLOUD_HOSTED",
]);
run_workers(
db.clone(),
rx.resubscribe(),

View File

@@ -836,7 +836,6 @@ impl RunJob {
/* scheduled_for_o */ None,
/* schedule_path */ None,
/* parent_job */ None,
/* root job */ None,
/* is_flow_step */ false,
/* running */ false,
None,

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.74.2
version: 1.70.1
title: Windmill API
contact:
@@ -2278,7 +2278,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)
operationId: deleteScriptByHash
tags:
- script
@@ -2293,23 +2293,6 @@ paths:
schema:
$ref: "#/components/schemas/Script"
/w/{workspace}/scripts/delete/p/{path}:
post:
summary: delete all scripts at a given path (require admin)
operationId: deleteScriptByPath
tags:
- script
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: script path
content:
application/json:
schema:
type: string
/w/{workspace}/scripts/get/p/{path}:
get:
summary: get script by path
@@ -2555,6 +2538,11 @@ paths:
required: true
schema:
type: string
- name: skip_direct
description: Skip checking that the node is part of the given flow.
in: query
schema:
type: boolean
responses:
"200":
description: job result
@@ -3349,22 +3337,6 @@ paths:
schema:
$ref: "#/components/schemas/CompletedJob"
/w/{workspace}/jobs/completed/get_result/{id}:
get:
summary: get completed job result
operationId: getCompletedJobResult
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/JobId"
responses:
"200":
description: result
content:
application/json:
schema: {}
/w/{workspace}/jobs/completed/delete/{id}:
post:
summary: delete completed job (erase content but keep run id)

View File

@@ -313,13 +313,10 @@ async fn create_app(
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Path(w_id): Path<String>,
Json(mut app): Json<CreateApp>,
Json(app): Json<CreateApp>,
) -> Result<(StatusCode, String)> {
let mut tx = user_db.begin(&authed).await?;
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
app.policy.on_behalf_of_email = Some(authed.email);
let id = sqlx::query_scalar!(
"INSERT INTO app
(workspace_id, path, summary, policy, versions)
@@ -466,9 +463,7 @@ async fn update_app(
sqlb.set_str("summary", nsummary);
}
if let Some(mut npolicy) = ns.policy {
npolicy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
npolicy.on_behalf_of_email = Some(authed.email);
if let Some(npolicy) = ns.policy {
sqlb.set(
"policy",
&format!(
@@ -675,7 +670,6 @@ async fn execute_component(
None,
None,
None,
None,
false,
false,
None,
@@ -733,9 +727,6 @@ fn build_args(
path: String,
args: &Map<String, Value>,
) -> Result<Map<String, Value>> {
// disallow var and res access in args coming from the user for security reasons
args.into_iter()
.try_for_each(|x| disallow_var_res_access(x.1))?;
let static_args = policy
.triggerables
.get(&path)
@@ -756,20 +747,3 @@ fn build_args(
}
Ok(args)
}
fn disallow_var_res_access(args: &serde_json::Value) -> Result<()> {
match args {
Value::Object(v) => v.into_iter().try_for_each(|x| disallow_var_res_access(x.1)),
Value::Array(arr) => arr.into_iter().try_for_each(|v| disallow_var_res_access(v)),
Value::String(s) => {
if s.starts_with("$var:") || s.starts_with("$res:") {
Err(Error::BadRequest(format!(
"For security reasons, variable or resource access is not allowed as dynamic argument"
)))
} else {
Ok(())
}
}
_ => Ok(()),
}
}

View File

@@ -7,12 +7,11 @@
*/
use axum::{
extract::{Extension, Path, Query},
extract::{Extension, Path},
routing::{get, post, put},
Json, Router,
};
use hyper::{HeaderMap, StatusCode};
use serde::Deserialize;
use hyper::StatusCode;
use windmill_common::{
error::{JsonResult, Result},
utils::{not_found_if_none, StripPath},
@@ -20,7 +19,6 @@ use windmill_common::{
use crate::{
db::{UserDB, DB},
jobs::add_include_headers,
users::Authed,
};
@@ -85,21 +83,13 @@ pub async fn new_payload(
Ok(StatusCode::CREATED)
}
#[derive(Deserialize, Clone)]
pub struct IncludeHeaderQuery {
include_header: Option<String>,
}
pub async fn update_payload(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
Query(run_query): Query<IncludeHeaderQuery>,
headers: HeaderMap,
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
Json(payload): Json<serde_json::Value>,
) -> Result<StatusCode> {
let mut tx = db.begin().await?;
let args = add_include_headers(&run_query.include_header, headers, args.unwrap_or_default());
sqlx::query!(
"
UPDATE capture
@@ -109,7 +99,7 @@ pub async fn update_payload(
",
&w_id,
&path.to_path(),
serde_json::json!(args),
&payload,
)
.execute(&mut tx)
.await?;

View File

@@ -237,7 +237,6 @@ async fn create_flow(
None,
None,
None,
None,
false,
false,
None,
@@ -394,7 +393,6 @@ async fn update_flow(
None,
None,
None,
None,
false,
false,
None,
@@ -640,6 +638,8 @@ mod tests {
"type": "script",
"path": "test"
},
"stop_after_if": null,
"summary": null
},
{
"id": "b",
@@ -648,12 +648,15 @@ mod tests {
"input_transforms": {},
"type": "rawscript",
"content": "test",
"lock": null,
"path": null,
"language": "deno"
},
"stop_after_if": {
"expr": "foo = 'bar'",
"skip_if_stopped": false
}
},
"summary": null
},
{
"id": "c",
@@ -675,7 +678,8 @@ mod tests {
"stop_after_if": {
"expr": "previous.isEmpty()",
"skip_if_stopped": false,
}
},
"summary": null
}
],
"failure_module": {
@@ -689,7 +693,8 @@ mod tests {
"stop_after_if": {
"expr": "previous.isEmpty()",
"skip_if_stopped": false
}
},
"summary": null
}
});
assert_eq!(dbg!(serde_json::json!(fv)), dbg!(expect));

View File

@@ -102,9 +102,10 @@ pub fn global_service() -> Router {
async fn get_result_by_id(
Extension(db): Extension<DB>,
Path((w_id, flow_id, node_id)): Path<(String, Uuid, String)>,
Query(ResultByIdQuery { skip_direct }): Query<ResultByIdQuery>,
Path((w_id, flow_id, node_id)): Path<(String, String, String)>,
) -> windmill_common::error::JsonResult<serde_json::Value> {
let res = windmill_queue::get_result_by_id(db, w_id, flow_id, node_id).await?;
let res = windmill_queue::get_result_by_id(db, skip_direct, w_id, flow_id, node_id).await?;
Ok(Json(res))
}
@@ -176,6 +177,11 @@ async fn get_job(
Ok(Json(job))
}
#[derive(Deserialize)]
pub struct ResultByIdQuery {
pub skip_direct: bool,
}
pub async fn get_job_by_id<'c>(
mut tx: Transaction<'c, Postgres>,
w_id: &str,
@@ -289,38 +295,28 @@ impl RunJobQuery {
fn add_include_headers(
&self,
headers: HeaderMap,
args: serde_json::Map<String, serde_json::Value>,
mut args: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
return add_include_headers(&self.include_header, headers, args);
let whitelist = self
.include_header
.as_ref()
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default();
whitelist
.iter()
.chain(INCLUDE_HEADERS.iter())
.for_each(|h| {
if let Some(v) = headers.get(h) {
args.insert(
h.to_string().to_lowercase().replace('-', "_"),
serde_json::Value::String(v.to_str().unwrap().to_string()),
);
}
});
args
}
}
pub fn add_include_headers(
include_header: &Option<String>,
headers: HeaderMap,
mut args: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
if include_header.is_none() {
return args;
}
let whitelist = include_header
.as_ref()
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default();
whitelist
.iter()
.chain(INCLUDE_HEADERS.iter())
.for_each(|h| {
if let Some(v) = headers.get(h) {
args.insert(
h.to_string().to_lowercase().replace('-', "_"),
serde_json::Value::String(v.to_str().unwrap().to_string()),
);
}
});
args
}
#[derive(Deserialize)]
pub struct ListQueueQuery {
pub script_path_start: Option<String>,
@@ -1072,8 +1068,6 @@ impl From<UnifiedJob> for Job {
visible_to_owner: uj.visible_to_owner,
suspend: uj.suspend,
mem_peak: uj.mem_peak,
root_job: None,
leaf_jobs: None,
}),
t => panic!("job type {} not valid", t),
}
@@ -1169,7 +1163,6 @@ pub async fn run_flow_by_path(
scheduled_for,
None,
run_query.parent_job,
run_query.parent_job,
false,
false,
None,
@@ -1205,7 +1198,6 @@ pub async fn run_job_by_path(
scheduled_for,
None,
run_query.parent_job,
run_query.parent_job,
false,
false,
None,
@@ -1358,7 +1350,6 @@ pub async fn run_wait_result_job_by_path(
scheduled_for,
None,
run_query.parent_job,
run_query.parent_job,
false,
false,
None,
@@ -1405,7 +1396,6 @@ pub async fn run_wait_result_job_by_hash(
scheduled_for,
None,
run_query.parent_job,
run_query.parent_job,
false,
false,
None,
@@ -1451,7 +1441,6 @@ pub async fn run_wait_result_flow_by_path(
scheduled_for,
None,
run_query.parent_job,
run_query.parent_job,
false,
false,
None,
@@ -1514,7 +1503,6 @@ async fn run_preview_job(
scheduled_for,
None,
None,
None,
false,
false,
None,
@@ -1548,7 +1536,6 @@ async fn run_preview_flow_job(
scheduled_for,
None,
None,
None,
false,
false,
None,
@@ -1584,7 +1571,6 @@ pub async fn run_job_by_hash(
scheduled_for,
None,
run_query.parent_job,
run_query.parent_job,
false,
false,
None,
@@ -1789,7 +1775,6 @@ async fn get_completed_job(
.fetch_optional(&db)
.await?;
tracing::info!("job_o: {:?}", job_o);
let job = not_found_if_none(job_o, "Completed Job", id.to_string())?;
Ok(Json(job))
}

View File

@@ -734,10 +734,14 @@ async fn slack_command(
.map_err(|_| error::Error::BadRequest("invalid payload".to_string()))?;
let body = String::from_utf8_lossy(&body);
if let Some(sv) = SLACK_SIGNING_SECRET.as_ref() {
if sv.verify(&ts, &body, &sig).ok().is_none() {
return Err(error::Error::BadRequest("verification failed".to_owned()));
}
if SLACK_SIGNING_SECRET
.as_ref()
.as_ref()
.map(|sv| sv.verify(&ts, &body, &sig).ok())
.flatten()
.is_none()
{
return Err(error::Error::BadRequest("verification failed".to_owned()));
}
let mut tx = db.begin().await?;
@@ -781,7 +785,6 @@ async fn slack_command(
None,
None,
None,
None,
false,
false,
None,

View File

@@ -70,7 +70,6 @@ pub fn workspaced_service() -> Router {
.route("/exists/p/*path", get(exists_script_by_path))
.route("/archive/h/:hash", post(archive_script_by_hash))
.route("/delete/h/:hash", post(delete_script_by_hash))
.route("/delete/p/*path", post(delete_script_by_path))
.route("/get/h/:hash", get(get_script_by_hash))
.route("/raw/h/:hash", get(raw_script_by_hash))
.route("/deployment_status/h/:hash", get(get_deployment_status))
@@ -124,7 +123,6 @@ async fn list_scripts(
AND workspace_id = ?)"
.bind(&w_id),
);
sqlb.and_where_eq("archived", true);
} else {
sqlb.and_where_eq("archived", false);
}
@@ -379,7 +377,6 @@ async fn create_script(
None,
None,
None,
None,
false,
false,
None,
@@ -721,46 +718,6 @@ async fn delete_script_by_hash(
Ok(Json(script))
}
async fn delete_script_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<String> {
let mut tx = user_db.begin(&authed).await?;
let path = path.to_path();
require_admin(authed.is_admin, &authed.username)?;
let script = sqlx::query_scalar!(
"DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path",
path,
w_id
)
.fetch_one(&db)
.await
.map_err(|e| Error::InternalErr(format!("deleting script by path {w_id}: {e}")))?;
audit_log(
&mut tx,
&authed.username,
"scripts.delete",
ActionKind::Delete,
&w_id,
Some(&path),
Some([("workspace", w_id.as_str())].into()),
)
.await?;
tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteScriptPath { workspace: w_id, path: path.to_string() },
);
Ok(Json(script))
}
async fn parse_python_code_to_jsonschema(
Json(code): Json<String>,
) -> JsonResult<windmill_parser::MainArgSignature> {

View File

@@ -1311,9 +1311,7 @@ async fn create_user(
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
let _ = HTTP_CLIENT
.post(&new_user_webhook)
.json(
&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "global_add"}),
)
.json(&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "new_user"}))
.send()
.await
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));

View File

@@ -37,7 +37,6 @@ pub enum WebhookMessage {
CreateScript { workspace: String, path: String, hash: String },
UpdateScript { workspace: String, path: String, hash: String },
DeleteScript { workspace: String, hash: String },
DeleteScriptPath { workspace: String, path: String },
CreateVariable { workspace: String, path: String },
UpdateVariable { workspace: String, old_path: String, new_path: String },
DeleteVariable { workspace: String, path: String },

View File

@@ -965,7 +965,7 @@ async fn invite_user(
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
let _ = &HTTP_CLIENT
.post(&new_user_webhook)
.json(&serde_json::json!({"email" : &nu.email, "event": "workspace_invite"}))
.json(&serde_json::json!({"email" : &nu.email, "event": "new_invite"}))
.send()
.await
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
@@ -1002,15 +1002,6 @@ async fn add_user(
tx.commit().await?;
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
let _ = HTTP_CLIENT
.post(&new_user_webhook)
.json(&serde_json::json!({"email" : &nu.email, "event": "workspace_add"}))
.send()
.await
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
}
Ok((
StatusCode::CREATED,
format!("user with email {} added", nu.email),

View File

@@ -125,7 +125,7 @@ pub enum FlowStatusModule {
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub enum JobResult {
SingleJob(Uuid),
ListJob(Vec<Uuid>),

View File

@@ -6,7 +6,7 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::collections::HashMap;
use std::{collections::HashMap, str::FromStr};
use anyhow::Context;
use reqwest::Client;
@@ -152,24 +152,55 @@ pub async fn pull(
pub async fn get_result_by_id(
db: Pool<Postgres>,
mut skip_direct: bool,
w_id: String,
flow_id: Uuid,
flow_id: String,
node_id: String,
) -> error::Result<serde_json::Value> {
let job_result: Option<JobResult> = sqlx::query_scalar!(
"SELECT leaf_jobs->$1::text FROM queue WHERE COALESCE((SELECT root_job FROM queue WHERE id = $2), $2) = id AND workspace_id = $3",
node_id,
flow_id,
w_id,
)
.fetch_optional(&db)
.await?
.flatten()
.map(|x| serde_json::from_value(x).ok())
.flatten();
let mut result_id: Option<JobResult> = None;
let mut parent_id = Uuid::from_str(&flow_id).ok();
while result_id.is_none() && parent_id.is_some() {
if !skip_direct {
let r = sqlx::query!(
"SELECT flow_status, parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT flow_status, parent_job FROM queue WHERE id = $1 AND workspace_id = $2 ",
parent_id.unwrap(),
w_id,
)
.fetch_optional(&db)
.await?;
if let Some(r) = r {
let value = r
.flow_status
.as_ref()
.ok_or_else(|| Error::InternalErr(format!("requiring a flow status value")))?
.to_owned();
parent_id = r.parent_job;
let status_o = serde_json::from_value::<FlowStatus>(value).ok();
result_id = status_o.and_then(|status| {
status
.modules
.iter()
.find(|m| m.id() == node_id)
.and_then(|m| m.job_result())
});
} else {
parent_id = None;
}
} else {
let q_parent = sqlx::query_scalar!(
"SELECT parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT parent_job FROM queue WHERE id = $1 AND workspace_id = $2",
parent_id.unwrap(),
w_id,
)
.fetch_optional(&db)
.await?
.flatten();
parent_id = q_parent;
skip_direct = false
}
}
let result_id = windmill_common::utils::not_found_if_none(
job_result,
result_id,
"Flow result by id",
format!("{}, {}", flow_id, node_id),
)?;
@@ -251,7 +282,6 @@ pub async fn push<'c>(
scheduled_for_o: Option<chrono::DateTime<chrono::Utc>>,
schedule_path: Option<String>,
parent_job: Option<Uuid>,
root_job: Option<Uuid>,
is_flow_step: bool,
mut same_worker: bool,
pre_run_error: Option<&windmill_common::error::Error>,
@@ -504,13 +534,12 @@ pub async fn push<'c>(
.unwrap_or_else(|| (None, None));
let flow_status = raw_flow.as_ref().map(FlowStatus::new);
let uuid = sqlx::query_scalar!(
"INSERT INTO queue
(workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for,
script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, \
flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner, root_job)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22, $23) \
flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22) \
RETURNING id",
workspace_id,
job_id,
@@ -533,8 +562,7 @@ pub async fn push<'c>(
same_worker,
pre_run_error.map(|e| e.to_string()),
email,
visible_to_owner,
root_job
visible_to_owner
)
.fetch_one(&mut tx)
.await
@@ -647,10 +675,6 @@ pub struct QueuedJob {
pub suspend: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mem_peak: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub root_job: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub leaf_jobs: Option<serde_json::Value>,
}
impl QueuedJob {

View File

@@ -85,7 +85,6 @@ pub async fn push_scheduled_job<'c>(
Some(next),
Some(schedule.path.clone()),
None,
None,
false,
false,
None,

View File

@@ -40,7 +40,7 @@ pub async fn eval_timeout(
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
let base_internal_url: String = base_internal_url.to_string();
timeout(
std::time::Duration::from_millis(3000),
std::time::Duration::from_millis(2000),
tokio::task::spawn_blocking(move || {
let mut ops = vec![];
@@ -104,7 +104,7 @@ pub async fn eval_timeout(
isolate.terminate_execution();
};
Error::ExecutionErr(format!(
"The expression of evaluation `{expr2}` took too long to execute (>3000ms)"
"The expression of evaluation `{expr2}` took too long to execute (>2000ms)"
))
})??
}
@@ -145,7 +145,7 @@ fn add_closing_bracket(s: &str) -> String {
s
}
const SPLIT_PAT: &str = ";";
const SPLIT_PAT: &str = ";\n";
async fn eval(
context: &mut JsRuntime,
expr: &str,
@@ -154,21 +154,14 @@ async fn eval(
by_id: Option<IdContext>,
base_internal_url: &str,
) -> anyhow::Result<serde_json::Value> {
let exprs = expr
.trim()
.split(SPLIT_PAT)
.map(|x| x.trim())
.filter(|x| !x.is_empty())
.collect::<Vec<&str>>();
let expr = if exprs.is_empty() {
"return undefined;".to_string()
} else {
format!(
"{};\n return {};",
exprs.iter().take(exprs.len() - 1).join(";\n"),
exprs.last().unwrap()
)
};
let expr = expr.trim();
let expr = format!(
"{}\nreturn {};",
expr.split(SPLIT_PAT)
.take(expr.split(SPLIT_PAT).count() - 1)
.join("\n"),
expr.split(SPLIT_PAT).last().unwrap_or_else(|| "")
);
let (api_code, by_id_code) = if let Some(EvalCreds { workspace, token }) = creds {
let by_id_code = if let Some(by_id) = by_id {
format!(
@@ -205,12 +198,12 @@ const results = new Proxy({{}}, {{
.into_iter()
.map(|(k, v)| {
let v_str = match v {
JobResult::SingleJob(x) => format!("\"{x}\""),
JobResult::SingleJob(x) => x.to_string(),
JobResult::ListJob(x) => {
format!("[{}]", x.iter().map(|x| format!("\"{x}\"")).join(","))
format!("[{}]", x.iter().map(|x| x.to_string()).join(","))
}
};
format!("\"{k}\": {v_str}")
format!("\"{k}\": \"{v_str}\"")
})
.join(","),
by_id.previous_id,
@@ -299,8 +292,9 @@ async fn op_get_result(args: Vec<String>) -> Result<serde_json::Value, anyhow::E
let base_url = &args[3];
let client = windmill_api_client::create_client(base_url, token.clone());
let result = client
.get_completed_job_result(workspace, &id.parse()?)
.get_completed_job(workspace, &id.parse()?)
.await?
.result
.clone();
Ok(serde_json::json!(result))
}
@@ -315,7 +309,7 @@ async fn op_get_id(args: Vec<String>) -> Result<Option<serde_json::Value>, anyho
let client = windmill_api_client::create_client(base_url, token.clone());
let result = client
.result_by_id(workspace, flow_job_id, node_id)
.result_by_id(workspace, flow_job_id, node_id, Some(true))
.await
.map_or(None, |e| Some(e.into_inner()));

View File

@@ -34,7 +34,7 @@ use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
process::{Child, Command},
sync::{
mpsc::{self, Sender}, watch, broadcast,
mpsc::{self, Sender}, watch,
},
time::{interval, sleep, Instant, MissedTickBehavior},
};
@@ -478,7 +478,7 @@ pub async fn run_worker(
let worker_sleep_duration_counter = prometheus::register_counter!(prometheus::opts!(
"worker_sleep_duration_counter",
"worker_execution_sleep_counter",
"Total number of seconds spent sleeping between pulling jobs from the queue"
)
.const_label("name", &worker_name))
@@ -493,7 +493,7 @@ pub async fn run_worker(
.expect("register prometheus metric");
let worker_pull_duration_counter = prometheus::register_counter!(prometheus::opts!(
"worker_pull_duration_counter",
"worker_pull_sleep_counter",
"Total number of seconds spent pulling jobs (if growing large the db is undersized)"
)
.const_label("name", &worker_name))
@@ -840,7 +840,7 @@ async fn handle_queued_job(
job_dir: &str,
metrics: Metrics,
same_worker_tx: Sender<Uuid>,
base_internal_url: &str,
base_internal_url: &str
) -> windmill_common::error::Result<()> {
if job.canceled {
return Err(Error::JsonErr(canceled_job_to_result(&job)))?;
@@ -888,10 +888,10 @@ async fn handle_queued_job(
logs.push_str(&format!("job {} on worker {}\n", &job.id, &worker_name));
let result = match job.job_kind {
JobKind::Dependencies => {
handle_dependency_job(&job, &mut logs, job_dir, db, worker_name).await
handle_dependency_job(&job, &mut logs, job_dir, db).await
}
JobKind::FlowDependencies => {
handle_flow_dependency_job(&job, &mut logs, job_dir, db, worker_name)
handle_flow_dependency_job(&job, &mut logs, job_dir, db)
.await
.map(|()| Value::Null)
}
@@ -912,8 +912,7 @@ async fn handle_queued_job(
job_dir,
worker_dir,
&mut logs,
base_internal_url,
worker_name
base_internal_url
)
.await
}
@@ -1065,9 +1064,7 @@ async fn handle_code_execution_job(
job_dir: &str,
worker_dir: &str,
logs: &mut String,
base_internal_url: &str,
worker_name: &str
base_internal_url: &str
) -> error::Result<serde_json::Value> {
let (inner_content, requirements_o, language) = match job.job_kind {
JobKind::Preview | JobKind::Script_Hub => (
@@ -1089,6 +1086,7 @@ async fn handle_code_execution_job(
"handle_code_execution_job should never be reachable with a non-code execution job"
),
};
let worker_name = worker_dir.split("/").last().unwrap_or("unknown");
let lang_str = job
.language
.as_ref()
@@ -1138,7 +1136,7 @@ mount {{
token,
&inner_content,
&shared_mount,
base_internal_url,
base_internal_url
)
.await
}
@@ -1153,8 +1151,7 @@ mount {{
&inner_content,
&shared_mount,
requirements_o,
base_internal_url,
worker_name
base_internal_url
)
.await
}
@@ -1169,8 +1166,7 @@ mount {{
job_dir,
requirements_o,
&shared_mount,
base_internal_url,
worker_name
base_internal_url
)
.await
}
@@ -1183,8 +1179,7 @@ mount {{
&inner_content,
job_dir,
&shared_mount,
base_internal_url,
worker_name
base_internal_url
)
.await
}
@@ -1213,7 +1208,6 @@ async fn handle_go_job(
requirements_o: Option<String>,
shared_mount: &str,
base_internal_url: &str,
worker_name: &str,
) -> Result<serde_json::Value, Error> {
//go does not like executing modules at temp root
let job_dir = &format!("{job_dir}/go");
@@ -1242,7 +1236,6 @@ async fn handle_go_job(
db,
true,
skip_go_mod,
worker_name
)
.await?;
@@ -1360,7 +1353,7 @@ func Run(req Req) (interface{{}}, error){{
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(&job.id, db, logs, build_go, false, worker_name).await?;
handle_child(&job.id, db, logs, build_go, false).await?;
Command::new(NSJAIL_PATH.as_str())
.current_dir(job_dir)
@@ -1386,7 +1379,7 @@ func Run(req Req) (interface{{}}, error){{
.stderr(Stdio::piped())
.spawn()?
};
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
read_result(job_dir).await
}
@@ -1400,7 +1393,6 @@ async fn handle_bash_job(
job_dir: &str,
shared_mount: &str,
base_internal_url: &str,
worker_name: &str,
) -> Result<serde_json::Value, Error> {
logs.push_str("\n\n--- BASH CODE EXECUTION ---\n");
set_logs(logs, &job.id, db).await;
@@ -1464,7 +1456,7 @@ async fn handle_bash_job(
.stderr(Stdio::piped())
.spawn()?
};
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
//for now bash jobs have an empty result object
Ok(serde_json::json!(logs
.lines()
@@ -1492,8 +1484,7 @@ async fn handle_deno_job(
inner_content: &String,
shared_mount: &str,
lockfile: Option<String>,
base_internal_url: &str,
worker_name: &str
base_internal_url: &str
) -> error::Result<serde_json::Value> {
logs.push_str("\n\n--- DENO CODE EXECUTION ---\n");
set_logs(logs, &job.id, db).await;
@@ -1628,7 +1619,7 @@ run().catch(async (e) => {{
}
.instrument(trace_span!("create_deno_jail"))
.await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
read_result(job_dir).await
}
@@ -1666,7 +1657,7 @@ async fn handle_python_job(
token: String,
inner_content: &String,
shared_mount: &str,
base_internal_url: &str,
base_internal_url: &str
) -> error::Result<serde_json::Value> {
create_dependencies_dir(job_dir).await;
@@ -1680,7 +1671,7 @@ async fn handle_python_job(
if requirements.is_empty() {
"".to_string()
} else {
pip_compile(&job.id, &requirements, logs, job_dir, db, worker_name)
pip_compile(&job.id, &requirements, logs, job_dir, db)
.await
.map_err(|e| {
Error::ExecutionErr(format!("pip compile failed: {}", e.to_string()))
@@ -1897,7 +1888,7 @@ mount {{
.spawn()?
};
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
read_result(job_dir).await
}
@@ -1927,7 +1918,6 @@ async fn handle_dependency_job(
logs: &mut String,
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
worker_name: &str,
) -> error::Result<serde_json::Value> {
let content = capture_dependency_job(
&job.id,
@@ -1942,8 +1932,7 @@ async fn handle_dependency_job(
.unwrap_or_else(|| "no raw code"),
logs,
job_dir,
db,
worker_name
db
)
.await;
match content {
@@ -1978,7 +1967,6 @@ async fn handle_flow_dependency_job(
logs: &mut String,
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
worker_name: &str,
) -> error::Result<()> {
let path = job.script_path.clone().ok_or_else(|| {
error::Error::InternalErr(
@@ -2009,7 +1997,6 @@ async fn handle_flow_dependency_job(
logs,
job_dir,
db,
worker_name
)
.await;
match new_lock {
@@ -2124,13 +2111,12 @@ async fn capture_dependency_job(
job_raw_code: &str,
logs: &mut String,
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
worker_name: &str
db: &sqlx::Pool<sqlx::Postgres>
) -> error::Result<String> {
match job_language {
ScriptLang::Python3 => {
create_dependencies_dir(job_dir).await;
pip_compile(job_id, job_raw_code, logs, job_dir, db, worker_name).await
pip_compile(job_id, job_raw_code, logs, job_dir, db ).await
}
ScriptLang::Go => {
install_go_dependencies(
@@ -2141,7 +2127,6 @@ async fn capture_dependency_job(
db,
false,
false,
worker_name
)
.await
}
@@ -2159,7 +2144,6 @@ async fn pip_compile(
logs: &mut String,
job_dir: &str,
db: &Pool<Postgres>,
worker_name: &str
) -> error::Result<String> {
logs.push_str(&format!("\nresolving dependencies..."));
set_logs(logs, job_id, db).await;
@@ -2192,7 +2176,7 @@ async fn pip_compile(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, child, false, worker_name)
handle_child(job_id, db, logs, child, false)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
let path_lock = format!("{job_dir}/requirements.txt");
@@ -2215,7 +2199,6 @@ async fn install_go_dependencies(
db: &sqlx::Pool<sqlx::Postgres>,
preview: bool,
skip_go_mod: bool,
worker_name: &str
) -> error::Result<String> {
if !skip_go_mod {
gen_go_mymod(code, job_dir).await?;
@@ -2226,7 +2209,7 @@ async fn install_go_dependencies(
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, child, false, worker_name).await?;
handle_child(job_id, db, logs, child, false).await?;
}
let child = Command::new(GO_PATH.as_str())
.current_dir(job_dir)
@@ -2234,7 +2217,7 @@ async fn install_go_dependencies(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, child, false, worker_name)
handle_child(job_id, db, logs, child, false)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
@@ -2340,7 +2323,6 @@ async fn handle_child(
logs: &mut String,
mut child: Child,
nsjail: bool,
worker_name: &str,
) -> error::Result<()> {
let update_job_interval = Duration::from_millis(500);
let write_logs_delay = Duration::from_millis(500);
@@ -2354,14 +2336,11 @@ async fn handle_child(
tracing::info!("could not get child pid");
}
let (set_too_many_logs, mut too_many_logs) = watch::channel::<bool>(false);
let (tx, mut rx) = broadcast::channel::<()>(3);
let mut rx2 = tx.subscribe();
let output = child_joined_output_stream(&mut child);
let job_id = job_id.clone();
let (tx, mut rx) = mpsc::channel::<()>(1);
/* the cancellation future is polled on by `wait_on_child` while
* waiting for the child to exit normally */
@@ -2371,22 +2350,10 @@ async fn handle_child(
let mut interval = interval(update_job_interval);
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
let mut i = 1;
loop {
tokio::select!(
_ = rx.recv() => break,
_ = interval.tick() => {
// update the last_ping column every 5 seconds
i+=1;
if i % 10 == 0 {
sqlx::query!(
"UPDATE worker_ping SET ping_at = now() WHERE worker = $1",
&worker_name
)
.execute(&db)
.await
.expect("update worker ping");
}
let mem_peak = get_mem_peak(pid, nsjail).await;
tracing::info!("{job_id} still running. mem peak: {}kB", mem_peak);
let mem_peak = if mem_peak > 0 { Some(mem_peak) } else { None };
@@ -2420,10 +2387,10 @@ async fn handle_child(
biased;
result = child.wait() => return result.map(Ok),
Ok(()) = too_many_logs.changed() => KillReason::TooManyLogs,
_ = sleep(*TIMEOUT_DURATION) => KillReason::Timeout,
_ = update_job => KillReason::Cancelled,
_ = sleep(*TIMEOUT_DURATION) => KillReason::Timeout,
};
tx.send(()).expect("rx should never be dropped");
tx.send(()).await.expect("rx should never be dropped");
drop(tx);
let set_reason = async {
@@ -2446,7 +2413,7 @@ async fn handle_child(
}
}
};
/* send SIGKILL and reap child process */
let (_, kill) = future::join(set_reason, child.kill()).await;
kill.map(|()| Err(kill_reason))
@@ -2462,13 +2429,12 @@ async fn handle_child(
/* log_remaining is zero when output limit was reached */
let mut log_remaining = max_log_size.saturating_sub(logs.chars().count());
let mut result = io::Result::Ok(());
let mut output = output.take_until(rx2.recv()).boxed();
let mut output = output;
/* `do_write` resolves the task, but does not contain the Result.
* It's useful to know if the task completed. */
let (mut do_write, mut write_result) = tokio::spawn(ready(())).remote_handle();
while let Some(line) = output.by_ref().next().await {
while let Some(line) = output.by_ref().next().await {
let do_write_ = do_write.shared();
let mut read_lines = stream::once(async { line })
@@ -2483,14 +2449,11 @@ async fn handle_child(
let mut joined = String::new();
while let Some(line) = read_lines.next().await {
match line {
Ok(_) if log_remaining == 0 => (),
Ok(line) => {
if line.is_empty() {
continue;
}
append_with_limit(&mut joined, &line, &mut log_remaining);
if log_remaining == 0 {
tracing::info!(%job_id, "Too many logs lines for job {job_id}");
let _ = set_too_many_logs.send(true);
@@ -2510,7 +2473,6 @@ async fn handle_child(
logs.push_str(&joined);
/* Ensure the last flush completed before starting a new one.
*
* This shouldn't pause since `take_until()` reads lines until `do_write`
@@ -2527,7 +2489,8 @@ async fn handle_child(
panic::resume_unwind(p);
}
(do_write, write_result) = tokio::spawn(append_logs(job_id, joined, db.clone())).remote_handle();
(do_write, write_result) =
tokio::spawn(append_logs(job_id, joined, db.clone())).remote_handle();
if let Err(err) = result {
tracing::error!(%job_id, %err, "error reading output for job {job_id}: {err}");
@@ -2537,7 +2500,6 @@ async fn handle_child(
if *set_too_many_logs.borrow() {
break;
}
}
/* drop our end of the pipe */
@@ -2851,7 +2813,7 @@ async fn handle_python_reqs(
.spawn()?
};
let child = handle_child(&job.id, db, logs, child, false, worker_name).await;
let child = handle_child(&job.id, db, logs, child, false).await;
tracing::info!(
worker_name = %worker_name,
job_id = %job.id,

View File

@@ -326,21 +326,6 @@ pub async fn update_flow_status_after_job_completion(
)
.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?;
}
}
}
@@ -1252,7 +1237,6 @@ 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 (uuid, inner_tx) = push(
tx,
&flow_job.workspace_id,
@@ -1264,7 +1248,6 @@ async fn push_next_flow_job(
scheduled_for_o,
flow_job.schedule_path.clone(),
Some(flow_job.id),
root_job,
true,
continue_on_same_worker,
err,

View File

@@ -23,7 +23,7 @@ async function tryResolveWorkspace(
{ isError: false; value: Workspace } | { isError: true; error: string }
> {
const cache = (opts as any).__secret_workspace;
if (cache) return { isError: false, value: cache };
if (cache) return cache;
if (opts.workspace) {
const e = await getWorkspaceByName(opts.workspace);
@@ -53,6 +53,7 @@ export async function resolveWorkspace(
): Promise<Workspace> {
const res = await tryResolveWorkspace(opts);
if (res.isError) {
console.log(res.error);
return Deno.exit(-1);
} else {
return res.value;
@@ -61,8 +62,8 @@ export async function resolveWorkspace(
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
const workspace = await resolveWorkspace(opts);
let token = await tryGetLoginInfo(opts);
let token = await tryGetLoginInfo(opts);
if (!token) {
token = workspace.token;
}
@@ -79,9 +80,9 @@ export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo>
if (!newToken) {
throw new Error("Could not reauth");
}
removeWorkspace(workspace.name, false, opts);
removeWorkspace(workspace.name);
workspace.token = newToken;
addWorkspace(workspace, opts);
addWorkspace(workspace);
setClient(
token,

View File

@@ -14,7 +14,7 @@ export {
DenoLandProvider,
UpgradeCommand,
} from "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts";
export { CompletionsCommand } from "https://deno.land/x/cliffy@v0.25.7/command/completions/mod.ts";
// std
export * as path from "https://deno.land/std@0.176.0/path/mod.ts";
export { ensureDir } from "https://deno.land/std@0.176.0/fs/ensure_dir.ts";

View File

@@ -192,7 +192,7 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
}
async function run(
opts: GlobalOptions & {
data?: string;
input: string[];
silent: boolean;
},
path: string,
@@ -200,8 +200,7 @@ async function run(
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
const input = opts.data ? await resolve(opts.data) : {};
const input = await resolve(opts.input);
const id = await JobService.runFlowByPath({
workspace: workspace.workspaceId,
@@ -237,7 +236,6 @@ async function run(
if (!opts.silent) {
console.log(colors.green.underline.bold("Flow ran to completion"));
console.log()
}
const jobInfo = await JobService.getCompletedJob({
workspace: workspace.workspaceId,
@@ -259,8 +257,8 @@ const command = new Command()
.command("run", "run a flow by path.")
.arguments("<path:string>")
.option(
"-d --data <data:string>",
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
"-i --input [inputs...:string]",
"Inputs specified as JSON objects or simply as <name>=<value>. Supports file inputs using @<filename> and stdin using @- these also need to be formatted as JSON. Later inputs override earlier ones.",
)
.option(
"-s --silent",

View File

@@ -1,4 +1,4 @@
import { Command, CompletionsCommand, DenoLandProvider, UpgradeCommand } from "./deps.ts";
import { Command, DenoLandProvider, UpgradeCommand } from "./deps.ts";
import flow from "./flow.ts";
import script from "./script.ts";
import workspace from "./workspace.ts";
@@ -13,7 +13,7 @@ import sync from "./sync.ts";
import { tryResolveVersion } from "./context.ts";
import { GlobalOptions } from "./types.ts";
const VERSION = "v1.74.2";
const VERSION = "v1.70.1";
let command: any = new Command()
.name("wmill")
@@ -60,8 +60,7 @@ let command: any = new Command()
],
provider: new DenoLandProvider({ name: "wmill" }),
}),
)
.command("completions", new CompletionsCommand());
);
if (Number.parseInt(VERSION.replace("v", "").replace(".", "")) > 1700) {
command = command

View File

@@ -11,7 +11,6 @@ import {
Table,
} from "./deps.ts";
import { Any, array, decoverto, model, property } from "./decoverto.ts";
import { writeAllSync } from "https://deno.land/std@0.176.0/streams/mod.ts";
@model()
export class ScriptFile {
@@ -281,27 +280,50 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
.render();
}
export async function resolve(input: string): Promise<Record<string, any>> {
if (!input) {
throw new Error("No data given");
export async function resolve(inputs: string[]): Promise<Record<string, any>> {
let result = {};
if (!inputs) {
return result;
}
if (input == "@-") {
input = new TextDecoder().decode(await readAll(Deno.stdin));
} if (input[0] == "@") {
input = await Deno.readTextFile(input.substring(1));
}
try {
return JSON.parse(input);
} catch (e) {
console.error("Impossible to parse input as JSON", input)
throw e
for (const input of inputs) {
let data: string;
if (input.startsWith("@")) {
if (input == "@-") {
data = new TextDecoder().decode(await readAll(Deno.stdin));
} else {
data = await Deno.readTextFile(input.substring(1));
}
} else {
if (input.startsWith("{")) {
data = input;
} else {
const key = input.split("=", 1)[0];
const value = input.substring(key.length + 1);
let o;
try {
o = JSON.parse(value);
} catch {
o = value;
}
data = JSON.stringify(Object.fromEntries([[key, o]]));
}
}
let jsonObj;
try {
jsonObj = JSON.parse(data);
} catch {
jsonObj = data;
}
result = { ...result, ...jsonObj };
}
return result;
}
async function run(
opts: GlobalOptions & {
data?: string;
input: string[];
silent: boolean;
},
path: string,
@@ -309,8 +331,7 @@ async function run(
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
const input = opts.data ? await resolve(opts.data) : {};
const input = await resolve(opts.input);
const id = await JobService.runScriptByPath({
workspace: workspace.workspaceId,
path,
@@ -343,9 +364,7 @@ export async function track_job(workspace: string, id: string) {
const result = await JobService.getCompletedJob({ workspace, id });
console.log(result.logs);
console.log()
console.log(colors.bold.underline.green("Job Completed"));
console.log()
return;
} catch {
/* ignore */
@@ -384,7 +403,7 @@ export async function track_job(workspace: string, id: string) {
}
if (updates.new_logs) {
writeAllSync(Deno.stdout, new TextEncoder().encode(updates.new_logs));
console.log(updates.new_logs);
logOffset += updates.new_logs.length;
}
@@ -407,15 +426,12 @@ export async function track_job(workspace: string, id: string) {
if ((final_job.logs?.length ?? -1) > logOffset) {
console.log(final_job.logs!.substring(logOffset));
}
console.log("\n")
if (final_job.success) {
console.log(colors.bold.underline.green("Job Completed"));
} else {
console.log(colors.bold.underline.red("Job Completed"));
}
console.log()
} catch {
console.log("Job appears to have completed, but no data can be retrieved");
}
@@ -450,8 +466,8 @@ const command = new Command()
.command("run", "run a script by path")
.arguments("<path:string>")
.option(
"-d --data <data:string>",
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
"-i --input [inputs...:string]",
"Inputs specified as JSON objects or simply as <name>=<value>. Supports file inputs using @<filename> and stdin using @- these also need to be formatted as JSON. Later inputs override earlier ones.",
)
.option(
"-s --silent",

View File

@@ -9,11 +9,9 @@ import {
Input,
setClient,
Table,
UserService,
WorkspaceService,
} from "./deps.ts";
import { decoverto, model, property } from "./decoverto.ts";
import { requireLogin } from "./context.ts";
@model()
export class Workspace {
@@ -139,8 +137,6 @@ async function list(opts: GlobalOptions) {
}),
)
.render();
console.log('Active: ' + colors.green.bold(activeName || 'none'))
}
async function switchC(opts: GlobalOptions, workspaceName: string) {
@@ -312,20 +308,12 @@ async function remove(_opts: GlobalOptions, name: string) {
await removeWorkspace(name, false, _opts);
}
async function whoami(_opts: GlobalOptions) {
await requireLogin(_opts)
console.log(await UserService.globalWhoami())
const activeName = await getActiveWorkspaceName(_opts);
console.log('Active: ' + colors.green.bold(activeName || 'none'))
}
const command = new Command()
.description("workspace related commands")
.action(list as any)
.command("switch")
.complete("workspace", async () => (await allWorkspaces()).map((x) => x.name))
.description("Switch to another workspace")
.arguments("<workspace_name:string:workspace>")
.arguments("<workspace_name:string>")
.action(switchC as any)
.command("add")
.description("Add a workspace")
@@ -346,9 +334,6 @@ const command = new Command()
.command("remove")
.description("Remove a workspace")
.arguments("<workspace_name:string>")
.action(remove as any)
.command("whoami")
.description("Show the currently active user")
.action(whoami as any);
.action(remove as any);
export default command;

View File

@@ -38,9 +38,7 @@ services:
depends_on:
db:
condition: service_healthy
# volumes:
# - ./oauth.json/:/usr/src/app/oauth.json
windmill_worker:
image: ghcr.io/windmill-labs/windmill:main
deploy:
@@ -63,6 +61,7 @@ services:
# to mount the worker folder to debug,, KEEP_JOB_DIR=true and mount /tmp/windmill
volumes:
- worker_dependency_cache:/tmp/windmill/cache
# - ./oauth.json/:/usr/src/app/oauth.json
lsp:
image: ghcr.io/windmill-labs/windmill-lsp:latest

View File

@@ -1,6 +1,6 @@
# Developing
## Starting The Development Server
## Starting the Development Server
Once you've created a project and installed dependencies with `npm install` (or
`pnpm install` or `yarn`), start a development server:
@@ -17,7 +17,7 @@ In the root folder:
```bash
docker build . -t windmill
docker-compose up db windmill_server windmill_worker
docker-compose up db server
```
### 2. Backend is run by cargo
@@ -25,9 +25,9 @@ docker-compose up db windmill_server windmill_worker
**Prerequisites**
- Install Rust [as explained on the website](https://www.rust-lang.org/tools/install).
- Install llvm
- Install llvm
**On OSX:**
**on OSX:**
```bash
brew install llvm caddy gsed
@@ -36,20 +36,7 @@ docker-compose up db windmill_server windmill_worker
# now, restart your shell. You should now have the `lld` binary on your PATH.
```
- To test that you have Rust and Cargo installed run `cargo --version`
- In your terminal, go to the backend directory and run `cargo build`
- Run `cargo run`
**Known issue on M1 Mac while running `cargo build`**
- You may encounter `linking with cc failed` build time error.
- To solve this run:
```bash
echo 'export RUSTFLAGS="-L/opt/homebrew/opt/libomp/lib"' >> ~/.zshrc
source ~/.zshrc
```
**Do a Frontend Build**
In order to run the backend, you need to have a frontend build inside `frontend/build/`.
@@ -69,14 +56,6 @@ npm run build
# now, you'll have a `frontend/build` folder.
```
**Known issue while running `npm run build`**
- You may encounter `FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory` error.
- To solve this run:
```bash
export NODE_OPTIONS=--max_old_space_size=8096
```
- run `npm run build` again
In the root folder:
```bash
@@ -103,7 +82,7 @@ sudo caddy run --config ./Caddyfile
and then go to <http://localhost>
### 3. Backend is run by remote!
### Backend is run by remote!
```bash
sudo caddy run --config ./CaddyfileRemote
@@ -140,8 +119,8 @@ Recommended config for VS Code:
```json
"[svelte]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
```
- turn _format on save_ on

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.74.2",
"version": "1.70.1",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -15,19 +15,18 @@
"test": "playwright test --config=tests-out/playwright.config.js"
},
"devDependencies": {
"@playwright/test": "^1.31.1",
"@playwright/test": "^1.29.2",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0-next.589",
"@sveltejs/package": "^1.0.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@types/d3": "^7.4.0",
"@types/d3-zoom": "^3.0.2",
"@types/node": "^18.11.18",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.6",
"@windmill-labs/svelte-grid": "^5.1.4",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
"cssnano": "^5.1.14",
@@ -35,20 +34,16 @@
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-svelte3": "^4.0.0",
"ol": "^7.2.2",
"openapi-typescript-codegen": "^0.23.0",
"path-browserify": "^1.0.1",
"pdfjs-dist": "^3.4.120",
"postcss": "^8.4.18",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"shepherd.js": "^10.0.1",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
"svelte": "^3.55.1",
"svelte-awesome": "^3.0.0",
"svelte-awesome-color-picker": "^2.4.1",
"svelte-check": "^3.0.2",
"svelte-highlight": "^6.2.1",
"svelte-overlay": "^1.4.1",
@@ -76,7 +71,6 @@
"chart.js": "^3.9.1",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"d3-zoom": "^3.0.0",
"date-fns": "^2.29.3",
"fast-equals": "^4.0.3",
"highlight.js": "^11.7.0",
@@ -88,7 +82,7 @@
"svelte-autosize": "^1.0.1",
"svelte-chartjs": "^3.1.0",
"svelte-portal": "^2.2.0",
"svelte-select": "^5.3.1",
"svelte-select": "^5.0.2",
"tailwind-merge": "^1.9.1",
"vscode-ws-jsonrpc": "^2.0.1"
},

View File

@@ -47,7 +47,6 @@ declare module '@windmill-labs/svelte-grid' {
onTopId?: string
scroller?: undefined
sensor?: number
parentWidth?: number
}
export interface Slots<T> {

View File

@@ -50,23 +50,12 @@
.Template-editor span.mtk20 {
color: black !important;
}
::-webkit-scrollbar {
width: 9px;
height: 9px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(155, 155, 155, 0.5);
border: transparent;
}
}
@layer components {
/* Flow graph viewer -> Svelvet library internal class overwrite */
.Node {
display: flex !important;
cursor: pointer !important;
}
}

View File

@@ -2,10 +2,9 @@
import { Highlight } from 'svelte-highlight'
import { json } from 'svelte-highlight/languages'
import TableCustom from './TableCustom.svelte'
import { copyToClipboard, truncate } from '$lib/utils'
import { Button, Drawer, DrawerContent } from './common'
import { truncate } from '$lib/utils'
import { Button } from './common'
import autosize from 'svelte-autosize'
import { ClipboardCopy } from 'lucide-svelte'
export let result: any
export let requireHtmlApproval = false
@@ -87,36 +86,16 @@
return 'json'
}
let payload = ''
let jsonViewer: Drawer
</script>
<Drawer bind:this={jsonViewer} size="900px">
<DrawerContent title="Expanded Result" on:close={jsonViewer.closeDrawer}>
<svelte:fragment slot="actions">
<Button
on:click={() => copyToClipboard(JSON.stringify(result, null, 4))}
color="light"
size="xs"
>
<div class="flex gap-2 items-center">Copy to clipboard <ClipboardCopy /> </div>
</Button>
</svelte:fragment>
<Highlight language={json} code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')} />
</DrawerContent>
</Drawer>
<div class="inline-highlight">
{#if result != undefined}
{#if resultKind && resultKind != 'json'}
<div class="mb-2 text-gray-500 text-sm bg-gray-50/20">
as JSON&nbsp;<input class="windmillapp" type="checkbox" bind:checked={forceJson} /></div
as JSON&nbsp;<input type="checkbox" bind:checked={forceJson} /></div
>{/if}{#if typeof result == 'object' && Object.keys(result).length > 0}<div
class="mb-2 min-w-[400px] 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">
<button on:click={jsonViewer.openDrawer}>Expand JSON</button>
</div></div
class="mb-2 text-sm text-gray-700"
>The result keys are: <b>{truncate(Object.keys(result).join(', '), 50)}</b></div
>{/if}{#if !forceJson && resultKind == 'table-col'}<div
class="grid grid-flow-col-dense border border-gray-200 rounded-md "
>
@@ -244,8 +223,10 @@
><a rel="noreferrer" target="_blank" href={result['approvalPage']}>Approval Page</a></div
>
</div>
{:else}
<Highlight language={json} code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')} />
{:else}<Highlight
language={json}
code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')}
/>
{/if}
{:else}
<div class="text-gray-500 text-sm">No result</div>

View File

@@ -232,14 +232,8 @@
<Badge color={validCode ? 'green' : 'red'} class="min-w-[60px] mr-3">
{validCode ? 'Valid' : 'Invalid'}
</Badge>
<div id="script-tutorial-3" class="flex items-center divide-x">
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="pr-1"
disablePopup={!iconOnly}
>
<div class="flex items-center divide-x">
<Popover notClickable placement="bottom" disapperTimoout={0} class="pr-1" disablePopup={!iconOnly}>
<Button
color="light"
btnClasses="!font-medium !h-full"
@@ -251,15 +245,11 @@
>
+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"
@@ -271,15 +261,11 @@
>
+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"
@@ -291,15 +277,11 @@
>
+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"
@@ -311,15 +293,11 @@
>
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"
@@ -342,15 +320,11 @@
{/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"
@@ -368,13 +342,7 @@
</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"
@@ -386,7 +354,9 @@
>
Script
</Button>
<svelte:fragment slot="text">Script</svelte:fragment>
<svelte:fragment slot="text">
Script
</svelte:fragment>
</Popover>
</div>

View File

@@ -11,21 +11,17 @@
| undefined = undefined
</script>
<div class="inline-flex flex-row items-center truncated">
<div class="inline-flex flex-row items-center">
<span class="font-semibold">
{label}
</span>
<Required {required} class="!ml-0" />
{#if format && format != ''}
<span class="text-sm italic ml-1 text-indigo-800">
({format})
</span>
{:else}
<span class="text-sm italic ml-1 text-indigo-800">
({type ?? 'any'}{contentEncoding && contentEncoding != ''
? `, encoding: ${contentEncoding}`
: ''})</span
>
{/if}
<span class="text-sm italic ml-1 text-indigo-800">
({type ?? 'any'}{contentEncoding && contentEncoding != ''
? `, encoding: ${contentEncoding}`
: ''}{format && format != '' ? `, format: ${format}` : ''}{itemsType?.type
? ` of ${itemsType?.type}s`
: ''})</span
>
</div>

View File

@@ -1,34 +1,33 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { FlowService, ScheduleService, type Flow, type FlowModule } from '$lib/gen'
import { initHistory, redo, undo } from '$lib/history'
import { page } from '$app/stores'
import { FlowService, ScheduleService, type Flow } from '$lib/gen'
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, faEye, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
import { setContext } from 'svelte'
import { writable, type Writable } from 'svelte/store'
import { writable } from 'svelte/store'
import CenteredPage from './CenteredPage.svelte'
import { Button, ButtonPopup, ButtonPopupItem, UndoRedo } from './common'
import { Button, Drawer, DrawerContent } from './common'
import { dirtyStore } from './common/confirmationModal/dirtyStore'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { OFFSET } from './CronInput.svelte'
import ScriptEditorDrawer from './flows/content/ScriptEditorDrawer.svelte'
import FlowGraphViewer from './FlowGraphViewer.svelte'
import FlowEditor from './flows/FlowEditor.svelte'
import type { FlowState } from './flows/flowState'
import { dfs } from './flows/flowStore'
import { flowStateStore } from './flows/flowState'
import { flowStore } from './flows/flowStore'
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
import type { FlowEditorContext } from './flows/types'
import { cleanInputs } from './flows/utils'
import { Tour } from './tutorial'
export let initialPath: string = ''
export let selectedId: string | undefined
export let initialArgs: Record<string, any> = {}
export let loading = false
export let flowStore: Writable<Flow>
export let flowStateStore: Writable<FlowState>
export let tour = false
let pathError = ''
async function createSchedule(path: string) {
const { cron, args, enabled } = $scheduleStore
@@ -51,10 +50,7 @@
}
}
let loadingSave = false
async function saveFlow(leave: boolean): Promise<void> {
loadingSave = true
async function saveFlow(): Promise<void> {
const flow = cleanInputs($flowStore)
const { cron, args, enabled } = $scheduleStore
$dirtyStore = false
@@ -116,12 +112,8 @@
await createSchedule(flow.path)
}
}
loadingSave = false
if (leave) {
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
} else if (initialPath !== $flowStore.path) {
goto(`/flows/edit/${$flowStore.path}?workspace_id=${$workspaceStore}`)
}
sendUserToast(`Flow saved at ${$flowStore.path}`)
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
}
let timeout: NodeJS.Timeout | undefined = undefined
@@ -149,15 +141,10 @@
}, 500)
}
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
const selectedIdStore = writable<string>(selectedId)
const scheduleStore = writable<Schedule>({ args: {}, cron: '', enabled: false })
const previewArgsStore = writable<Record<string, any>>(initialArgs)
const scriptEditorDrawer = writable<ScriptEditorDrawer | undefined>(undefined)
const moving = writable<{ module: FlowModule; modules: FlowModule[] } | undefined>(undefined)
const history = initHistory($flowStore)
const testStepStore = writable<Record<string, any>>({})
function select(selectedId: string) {
selectedIdStore.set(selectedId)
@@ -166,13 +153,8 @@
setContext<FlowEditorContext>('FlowEditorContext', {
selectedId: selectedIdStore,
schedule: scheduleStore,
previewArgs: previewArgsStore,
scriptEditorDrawer,
moving,
history,
flowStateStore,
flowStore,
testStepStore
select,
previewArgs: previewArgsStore
})
async function loadSchedule() {
@@ -195,86 +177,38 @@
loadHubScripts()
function onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'Z':
if (event.ctrlKey) {
$flowStore = redo(history)
event.preventDefault()
}
break
case 'z':
if (event.ctrlKey) {
$flowStore = undo(history, $flowStore)
$selectedIdStore = 'Input'
event.preventDefault()
}
break
case 's':
if (event.ctrlKey) {
saveFlow(false)
event.preventDefault()
}
break
case 'ArrowDown': {
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > -1 && idx < ids.length - 1) {
$selectedIdStore = ids[idx + 1]
event.preventDefault()
}
break
}
case 'ArrowUp': {
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > 0 && idx < ids.length) {
$selectedIdStore = ids[idx - 1]
event.preventDefault()
}
break
}
}
}
function generateIds() {
return [
'settings-metadata',
'constants',
...dfs($flowStore.value.modules, (module) => module.id)
]
}
let flowViewer: Drawer
</script>
<svelte:window on:keydown={onKeyDown} />
{#if tour}
<Tour tutorial="flow" />
{/if}
{#if !$userStore?.operator}
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />
<UnsavedConfirmationModal />
<Drawer bind:this={flowViewer} size="75%">
<DrawerContent title="View Graph" on:close={flowViewer.closeDrawer} noPadding>
<div class="overflow-hidden h-full w-full">
<FlowGraphViewer flow={$flowStore} />
</div>
</DrawerContent>
</Drawer>
<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"
>
<div class="flex flex-row gap-4 items-center">
<div class="flex flex-row">
<FlowImportExportMenu />
<UndoRedo
undoProps={{ disabled: $history.index === 0 }}
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
on:undo={() => {
$flowStore = undo(history, $flowStore)
$selectedIdStore = 'Input'
}}
on:redo={() => {
$flowStore = redo(history)
}}
/>
<Button
btnClasses="inline-flex"
startIcon={{ icon: faEye }}
variant="border"
color="light"
size="sm"
on:click={flowViewer.openDrawer}
>
Graph
</Button>
</div>
<div class="gap-1 flex-row hidden md:flex shrink overflow-hidden">
{#if $scheduleStore.enabled}
<Button
@@ -321,22 +255,12 @@
<div class="flex flex-row space-x-2">
<FlowPreviewButtons />
<div class="center-center">
<ButtonPopup
loading={loadingSave}
size="sm"
<Button
disabled={pathError != ''}
startIcon={{ icon: faSave }}
on:click={() => saveFlow(false)}
size="sm"
on:click={saveFlow}>Save</Button
>
<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>
</div>
</div>
</div>

View File

@@ -79,13 +79,14 @@
</Drawer>
<div class="grid grid-cols-3 w-full">
<div
bind:clientHeight={topHeight}
class="{noSide
? 'col-span-3'
: 'sm:col-span-2 col-span-3'} w-full border border-gray-400 max-h-screen"
: 'sm:col-span-2 col-span-3'} w-full border border-gray-400 h-screen"
class:overflow-auto={overflowAuto}
>
<FlowGraph
minHeight={400}
minHeight={topHeight}
modules={flow?.value?.modules}
failureModule={flow?.value?.failure_module}
on:click={(e) => (stepDetail = e.detail)}
@@ -96,9 +97,7 @@
class="w-full border-r border-b border-t border-gray-400 min-h-[150px] p-2 overflow-auto hidden sm:block"
>
{#if stepDetail == undefined}
<SchemaViewer schema={flow?.schema} />
<span class="font-black text-lg w-full my-4 mt-14">
<span class="font-black text-lg w-full my-4">
<span>Click on a step to see its details</span>
</span>
{:else if stepDetail == 'Input'}

View File

@@ -12,9 +12,9 @@
<div
class:border={!noBorder}
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border-gray-400 h-full max-h-screen"
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border-gray-400 h-full"
>
<div class="bg-white {col ? '' : 'max-h-80'} h-full p-1 overflow-auto relative">
<div class="bg-white max-h-80 h-full p-1 overflow-auto relative">
<span class="text-gray-500">Result</span>
{#if result}
<DisplayResult {result} />
@@ -24,7 +24,7 @@
<div class="text-gray-400">No result (result is undefined)</div>
{/if}
</div>
<div class="overflow-auto {col ? '' : 'max-h-80'} h-full relative">
<div class="overflow-auto max-h-80 h-full relative">
<LogViewer content={logs ?? ''} isLoading={false} />
</div>
</div>

View File

@@ -5,12 +5,13 @@
import { Button, Kbd } from './common'
import { createEventDispatcher, getContext } from 'svelte'
import Icon from 'svelte-awesome'
import { dfs } from './flows/flowStore'
import { dfs, flowStore } from './flows/flowStore'
import type { FlowEditorContext } from './flows/types'
import { runFlowPreview } from './flows/utils'
import SchemaForm from './SchemaForm.svelte'
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
import FlowProgressBar from './flows/FlowProgressBar.svelte'
import { flowStateStore } from './flows/flowState'
import CapturePayload from './flows/content/CapturePayload.svelte'
import { Loader2 } from 'lucide-svelte'
@@ -20,11 +21,11 @@
export let jobId: string | undefined = undefined
export let job: Job | undefined = undefined
let isValid: boolean = true
let isRunning: boolean = false
let jobProgressReset: () => void
const { selectedId, previewArgs, flowStateStore, flowStore } =
getContext<FlowEditorContext>('FlowEditorContext')
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
function sliceModules(modules: FlowModule[], upTo: number, idOrders: string[]): FlowModule[] {
@@ -104,6 +105,7 @@
{#if isRunning}
<Button
disabled={!isValid}
color="red"
on:click={async () => {
isRunning = false
@@ -130,6 +132,7 @@
color="blue"
size="sm"
btnClasses="w-full max-w-lg"
disabled={!isValid}
on:click={() => runPreview($previewArgs)}
>
Test flow <Kbd class="ml-2">Ctrl+Enter</Kbd>
@@ -146,13 +149,14 @@
</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 ">
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
<SchemaForm
noVariablePicker
compact
class="py-4 max-w-3xl"
schema={$flowStore.schema}
bind:isValid
bind:args={$previewArgs}
/>
</div>

View File

@@ -96,7 +96,7 @@
localFlowModuleStates?.[innerModules?.[i - 1]?.id ?? '']?.type ==
FlowStatusModule.type.SUCCESS
) {
localFlowModuleStates[mod.id ?? ''] = { type: mod.type, args: job?.args }
localFlowModuleStates[mod.id ?? ''] = { type: mod.type }
} else if (
mod.type === FlowStatusModule.type.WAITING_FOR_EXECUTOR &&
localFlowModuleStates[mod.id ?? '']?.scheduled_for == undefined
@@ -109,8 +109,7 @@
type: mod.type,
scheduled_for: 'scheduled for ' + displayDate(job?.['scheduled_for'], true),
job_id: job?.id,
parent_module: mod['parent_module'],
args: job?.args
parent_module: mod['parent_module']
}
})
}
@@ -330,12 +329,10 @@
type: FlowStatusModule.type.IN_PROGRESS,
logs: e.detail.logs,
job_id: e.detail.id,
args: e.detail.args,
iteration_total: flowJobIds?.flowJobs.length
}
} else {
localFlowModuleStates[flowJobIds.moduleId] = {
args: e.detail.args,
type: e.detail.success
? FlowStatusModule.type.SUCCESS
: FlowStatusModule.type.FAILURE,
@@ -400,12 +397,10 @@
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,
@@ -452,17 +447,13 @@
<FlowGraph
success={isSuccess(job?.['success'])}
flowModuleStates={localFlowModuleStates}
on:select={(e) => {
if (typeof e.detail == 'string') {
if (e.detail == 'Input') {
selectedNode = 'start'
} else if (e.detail == 'Result') {
selectedNode = 'end'
} else {
selectedNode = e.detail
}
} else {
on:click={(e) => {
if (e.detail.id) {
selectedNode = e.detail.id
} else if (e.detail == 'Result') {
selectedNode = 'end'
} else if (e.detail == 'Input') {
selectedNode = 'start'
}
}}
modules={job.raw_flow?.modules ?? []}
@@ -506,10 +497,6 @@
</div>
{/if}
</div>
<div class="px-1 border-b border-black">
<JobArgs args={node.args} />
</div>
<FlowJobResult
loading={job['running'] == true}
noBorder

View File

@@ -82,10 +82,16 @@
}
function connectProperty(rawValue: string) {
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
arg.type = 'javascript'
propertyType = 'javascript'
monaco?.setCode(arg.expr)
if (isStaticTemplate(inputCat)) {
arg.value = `\$\{${rawValue}}`
setPropertyType(arg.value)
monacoTemplate?.setCode(arg.value)
} else {
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
arg.type = 'javascript'
propertyType = 'javascript'
monaco?.setCode(arg.expr)
}
}
function onFocus() {
@@ -148,7 +154,7 @@
{/if}
</div>
{#if !noDynamicToggle}
<div class="flex flex-row gap-x-4 gap-y-1 flex-wrap z-10">
<div class="flex flex-row gap-x-4 gap-y-1 flex-wrap">
<ToggleButtonGroup
bind:selected={propertyType}
on:selected={(e) => {
@@ -190,10 +196,10 @@
>
{#if isStaticTemplate(inputCat)}
<ToggleButton light position="left" value="static" size="xs">
{'${} '}&nbsp;
<Tooltip
>Write text or surround javascript with "{openBracket}" and "{closeBracket}". Use
`result` to connect to another node's output.
{'${} '}Template &nbsp; <Tooltip
>Write javascript expressions between "{openBracket}" and "{closeBracket}". You may
refer to contextual objects like 'flow_input', or 'result' or functions like
'resource' and 'variable'
</Tooltip></ToggleButton
>
{:else}
@@ -206,11 +212,9 @@
value="javascript"
startIcon={{ icon: faCode }}
size="xs"
>&nbsp;<Tooltip
>Write javascript expressions directly, using 'flow_input' or 'result'. You can use
multiline javascript.
</Tooltip></ToggleButton
>
Dynamic (JS)
</ToggleButton>
</ToggleButtonGroup>
<Button
variant="contained"
@@ -301,4 +305,6 @@
Not recognized input type {argName}
{/if}
</div>
{:else}
<p class="text-sm text-gray-700">Argument at {argName} is undefined</p>
{/if}

View File

@@ -1,114 +0,0 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { VariableService, type InputTransform } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue } from '$lib/utils'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { Button } from './common'
import InputTransformForm from './InputTransformForm.svelte'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
export let schema: Schema
export let args: Record<string, InputTransform | any> = {}
export let isValid: boolean = true
export let extraLib: string = 'missing extraLib'
export let previousModuleId: string | undefined = undefined
export let filter: string[] | undefined = undefined
export let noDynamicToggle = false
let clazz: string = ''
export { clazz as class }
let inputCheck: { [id: string]: boolean } = {}
$: isValid = allTrue(inputCheck) ?? false
$: if (args == undefined || typeof args !== 'object') {
args = {}
}
function removeExtraKey() {
const nargs = {}
Object.keys(args ?? {}).forEach((key) => {
if (keys.includes(key)) {
nargs[key] = args[key]
}
})
args = nargs
}
let pickForField: string | undefined
let itemPicker: ItemPicker | undefined = undefined
let variableEditor: VariableEditor | undefined = undefined
let keys: string[] = []
$: {
let lkeys = Object.keys(schema?.properties ?? {})
if (schema?.properties && JSON.stringify(lkeys) != JSON.stringify(keys)) {
keys = lkeys
removeExtraKey()
}
}
</script>
<div class="w-full {clazz}">
{#if keys.length > 0}
{#each keys as argName, i (argName)}
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
<div class="z-10">
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
bind:schema
bind:argName
bind:inputCheck={inputCheck[argName]}
bind:extraLib
{variableEditor}
{itemPicker}
bind:pickForField
{noDynamicToggle}
/>
</div>
{/if}
{/each}
{:else}
<div class="text-gray-500 text-sm">No inputs</div>
{/if}
</div>
<ItemPicker
bind:this={itemPicker}
pickCallback={(path, _) => {
if (pickForField) {
args[pickForField].value = '$var:' + path
}
}}
itemName="Variable"
extraField="path"
loadItems={async () =>
(await VariableService.listVariable({ workspace: $workspaceStore ?? '' })).map((x) => ({
name: x.path,
...x
}))}
>
<div
slot="submission"
class="flex flex-row-reverse w-full bg-white border-t border-gray-200 rounded-bl-lg rounded-br-lg"
>
<Button
variant="border"
color="blue"
size="sm"
startIcon={{ icon: faPlus }}
on:click={() => {
variableEditor?.initNew?.()
}}
>
New variable
</Button>
</div>
</ItemPicker>
<VariableEditor bind:this={variableEditor} />

View File

@@ -42,7 +42,7 @@
<button on:click={logViewer.openDrawer}>Expand</button>
<div class="py-2 pr-2 text-xs flex gap-2 items-center">
Auto scroll
<input class="windmillapp" type="checkbox" bind:checked={scroll} />
<input type="checkbox" bind:checked={scroll} />
</div>
</div>
</div>

View File

@@ -8,17 +8,14 @@
import LogViewer from './LogViewer.svelte'
import DisplayResult from './DisplayResult.svelte'
import Button from './common/button/Button.svelte'
import { flowStateStore, testStepStore } from './flows/flowState'
import { flowStore } from './flows/flowStore'
import { workspaceStore } from '$lib/stores'
import { Loader2 } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { FlowEditorContext } from './flows/types'
export let mod: FlowModule
export let schema: Schema
const { flowStore, flowStateStore, testStepStore } =
getContext<FlowEditorContext>('FlowEditorContext')
// Test
let testJobLoader: TestJobLoader
let testIsLoading = false
@@ -89,7 +86,6 @@
detailed={false}
topButton
bind:args={stepArgs}
isFlow={false}
/>
{#if testIsLoading}
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full mt-4" color="red" size="sm">

View File

@@ -167,7 +167,7 @@
{#if showOptions}
<ul
class="options"
transition:fly|local={{ duration: 200, y: 5 }}
transition:fly={{ duration: 200, y: 5 }}
on:mousedown|preventDefault={handleOptionMousedown}
>
{#each filtered as option}

View File

@@ -277,19 +277,15 @@
folderCreated = undefined
}}
>
{#if !folderCreated}
<div class="flex flex-row">
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
<Button
size="md"
startIcon={{ icon: faPlus }}
disabled={!newFolderName}
on:click={addFolder}
>
New&nbsp;folder
</Button>
</div>
{:else}
<div class="flex flex-row">
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
<Button size="md" startIcon={{ icon: faPlus }} disabled={!newFolderName} on:click={addFolder}>
New&nbsp;folder
</Button>
</div>
{#if folderCreated}
<div class="mt-8" />
<FolderEditor name={folderCreated} />
{/if}
</DrawerContent>
@@ -409,8 +405,8 @@
{/if}
</div>
<div class="flex-row flex justify-between w-full">
<div><span class="font-mono text-sm break-all">{path}</span></div>
<div class="flex-row flex justify-between">
<div><span class="font-mono text-sm">{path}</span></div>
<div class="text-red-600 text-2xs">{error}</div>
</div>
</div>

View File

@@ -49,11 +49,22 @@
</script>
{#if notClickable}
<span use:popperRef on:mouseenter={open} on:mouseleave={close} class={$$props.class}>
<span
use:popperRef
on:mouseenter={open}
on:mouseleave={close}
class={$$props.class}
>
<slot />
</span>
{:else}
<button use:popperRef on:mouseenter={open} on:mouseleave={close} on:click class={$$props.class}>
<button
use:popperRef
on:mouseenter={open}
on:mouseleave={close}
on:click
class={$$props.class}
>
<slot />
</button>
{/if}

View File

@@ -8,41 +8,19 @@
import Select from 'svelte-select'
import AppConnect from './AppConnect.svelte'
import ResourceEditor from './ResourceEditor.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
const dispatch = createEventDispatcher()
let resources: Resource[] = []
export let initialValue: string | undefined = undefined
export let value: string | undefined = initialValue
export let resourceType: string | undefined = undefined
let valueSelect =
initialValue || value
? {
value: value ?? initialValue,
label: value ?? initialValue
}
: undefined
let collection = [valueSelect]
async function loadResources(resourceType: string | undefined) {
const nc = (
await ResourceService.listResource({
workspace: $workspaceStore!,
resourceType
})
).map((x) => ({
value: x.path,
label: x.path
}))
if (!nc.find((x) => x.value == value) && (initialValue || value)) {
nc.push({ value: value ?? initialValue!, label: value ?? initialValue! })
}
collection = nc
const v = value
resources = await ResourceService.listResource({ workspace: $workspaceStore!, resourceType })
value = v
}
$: {
if ($workspaceStore) {
loadResources(resourceType)
@@ -50,6 +28,10 @@
}
$: dispatch('change', value)
$: collection = resources.map((x) => ({
value: x.path,
label: x.path
}))
let appConnect: AppConnect
let resourceEditor: ResourceEditor
</script>
@@ -58,7 +40,6 @@
on:refresh={async (e) => {
await loadResources(resourceType)
value = e.detail
valueSelect = { value: e.detail, label: e.detail }
}}
newPageOAuth
bind:this={appConnect}
@@ -68,29 +49,22 @@
bind:this={resourceEditor}
on:refresh={async (e) => {
await loadResources(resourceType)
console.log(e)
if (e.detail) {
value = e.detail
valueSelect = { value: e.detail, label: e.detail }
}
}}
/>
<div class="flex flex-row gap-x-1 w-full">
<Select
value={valueSelect}
on:change={(e) => {
value = e.detail.value
valueSelect = e.detail
}}
on:clear={() => {
value = undefined
valueSelect = undefined
}}
listAutoWidth={false}
--height="34px"
value={collection.find((x) => x.value == value)}
bind:justValue={value}
items={collection}
class="text-clip grow min-w-0"
placeholder="{resourceType} resource"
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
/>
{#if value && value != ''}
<Button variant="border" size="xs" on:click={() => resourceEditor?.initEdit?.(value ?? '')}>

View File

@@ -49,7 +49,6 @@
export let loading = false
export let noVariablePicker = false
export let viewCliRun = false
export let isFlow: boolean
export let args: Record<string, any> = decodeArgs($page.url.searchParams.get('args') ?? undefined)
@@ -65,9 +64,9 @@
let scheduledForStr: string | undefined
let invisible_to_owner: false
$: cliCommand = `wmill ${isFlow ? 'flow' : 'script'} run ${runnable?.path} -d '${JSON.stringify(
args
)}'`
$: cliCommand = `wmill ${runnable?.kind} run ${runnable?.path} ${Object.entries(args)
.map(([k, v]) => `-i ${k}=${JSON.stringify(v)}`)
.join(' ')}`
</script>
<div class="max-w-6xl">
@@ -146,7 +145,7 @@
</Button>
</div>
{#if viewOptions}
<div transition:slide|local class="mt-6">
<div transition:slide class="mt-6">
<div class="border rounded-md p-3 pt-4">
<div class="flex flex-row items-end">
<div class="w-max md:w-2/3 mt-2 mb-1">
@@ -214,14 +213,14 @@
<div class="my-10" />
<Button
color="light"
size="xs"
size="sm"
endIcon={{ icon: viewCliOptions ? faChevronUp : faChevronDown }}
on:click={() => (viewCliOptions = !viewCliOptions)}
>
Run it from the CLI
</Button>
{#if viewCliOptions}
<div transition:slide|local class="mt-2 px-4 pt-2">
<div transition:slide class="mt-2 px-4 pt-2">
<InlineCodeCopy content={cliCommand} />
<CliHelpBox />
</div>

View File

@@ -4,11 +4,14 @@
import { workspaceStore } from '$lib/stores'
import { allTrue } from '$lib/utils'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { slide } from 'svelte/transition'
import ArgInput from './ArgInput.svelte'
import { Button } from './common'
import InputTransformForm from './InputTransformForm.svelte'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
export let inputTransform = false
export let schema: Schema
export let args: Record<string, InputTransform | any> = {}
export let disabledArgs: string[] = []
@@ -16,12 +19,16 @@
export let editableSchema = false
export let isValid: boolean = true
export let extraLib: string = 'missing extraLib'
export let autofocus = false
export let previousModuleId: string | undefined = undefined
export let shouldHideNoInputs: boolean = false
export let compact = false
export let password: string | undefined = undefined
export let noVariablePicker = false
export let filter: string[] | undefined = undefined
export let noDynamicToggle = false
export let flexWrap = false
export let noDelete = false
@@ -36,19 +43,17 @@
}
function removeExtraKey() {
const nargs = {}
Object.keys(args ?? {}).forEach((key) => {
if (keys.includes(key)) {
nargs[key] = args[key]
if (!keys.includes(key)) {
delete args[key]
delete inputCheck[key]
}
})
args = nargs
}
let pickForField: string | undefined
let itemPicker: ItemPicker | undefined = undefined
let variableEditor: VariableEditor | undefined = undefined
let keys: string[] = []
$: {
let lkeys = Object.keys(schema?.properties ?? {})
@@ -64,60 +69,48 @@
<div class="w-full {clazz} {flexWrap ? 'flex flex-row flex-wrap gap-x-6 gap-y-2' : ''}">
{#if keys.length > 0}
{#each keys as argName, i (argName)}
{#if Object.keys(schema.properties ?? {}).includes(argName)}
<div>
{#if typeof args == 'object' && schema?.properties[argName]}
{#if editableSchema}
<ArgInput
autofocus={i == 0 && autofocus}
label={argName}
bind:description={schema.properties[argName].description}
bind:value={args[argName]}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
properties={schema.properties[argName].properties}
bind:itemsType={schema.properties[argName].items}
disabled={disabledArgs.includes(argName) || disabled}
{editableSchema}
{compact}
password={argName == password}
{variableEditor}
{itemPicker}
bind:pickForField
bind:extra={schema.properties[argName]}
/>
{:else}
<ArgInput
autofocus={i == 0 && autofocus}
label={argName}
description={schema.properties[argName].description}
bind:value={args[argName]}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
enum_={schema.properties[argName].enum}
format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
properties={schema.properties[argName].properties}
itemsType={schema.properties[argName].items}
disabled={disabledArgs.includes(argName) || disabled}
{editableSchema}
{compact}
password={argName == password}
{variableEditor}
{itemPicker}
bind:pickForField
extra={schema.properties[argName]}
/>
{/if}
{#if !filter || filter.includes(argName)}
<div transition:slide|local>
{#if inputTransform}
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
bind:schema
bind:argName
bind:inputCheck={inputCheck[argName]}
bind:extraLib
{variableEditor}
{itemPicker}
bind:pickForField
{noDynamicToggle}
/>
{:else if typeof args == 'object'}
<ArgInput
autofocus={i == 0 && autofocus}
label={argName}
bind:description={schema.properties[argName].description}
bind:value={args[argName]}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
properties={schema.properties[argName].properties}
bind:itemsType={schema.properties[argName].items}
disabled={disabledArgs.includes(argName) || disabled}
{editableSchema}
{compact}
password={argName == password}
{variableEditor}
{itemPicker}
bind:pickForField
bind:extra={schema.properties[argName]}
/>
{:else}
Expected argument to be an object, got {JSON.stringify(args)} instead
{/if}
</div>
{/if}
@@ -132,7 +125,11 @@
bind:this={itemPicker}
pickCallback={(path, _) => {
if (pickForField) {
args[pickForField] = '$var:' + path
if (inputTransform) {
args[pickForField].value = '$var:' + path
} else {
args[pickForField] = '$var:' + path
}
}
}}
itemName="Variable"

View File

@@ -12,8 +12,9 @@
import ScriptEditor from './ScriptEditor.svelte'
import ScriptSchema from './ScriptSchema.svelte'
import CenteredPage from './CenteredPage.svelte'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { dirtyStore } from './common/confirmationModal/dirtyStore'
import { Button, ButtonPopup, ButtonPopupItem, Kbd } from './common'
import { Button, Kbd } from './common'
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
@@ -53,9 +54,7 @@
script.content = initialCode(language, kind, template)
}
let loadingSave = false
async function editScript(leave: boolean): Promise<void> {
loadingSave = true
async function editScript(): Promise<void> {
try {
$dirtyStore = false
localStorage.removeItem(script.path)
@@ -84,17 +83,12 @@
kind: script.kind
}
})
if (leave) {
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
} else {
await goto(`/scripts/edit/${newHash}?step=2`)
script.hash = newHash
}
sendUserToast(`New script created at hash ${newHash}`)
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
} catch (error) {
sendUserToast(`Impossible to save the script: ${error.body || error.message}`, true)
sendUserToast(`Impossible to save the script: ${error.body}`, true)
}
loadingSave = false
}
async function changeStep(step: number) {
@@ -120,7 +114,10 @@
}
</script>
<svelte:window on:keydown={onKeyDown} />
{#if !$userStore?.operator}
<UnsavedConfirmationModal />
<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">
@@ -186,24 +183,14 @@
>
Next {#if step == 1}<Kbd>Enter</Kbd>{/if}
</Button>
<ButtonPopup
loading={loadingSave}
<Button
size="sm"
variant={step == 1 ? 'border' : 'contained'}
disabled={step === 1 && pathError !== ''}
btnClasses={step == 1 && initialPath == '' ? 'invisible' : ''}
startIcon={{ icon: faSave }}
on:click={() => editScript(false)}
on:click={editScript}>Save</Button
>
<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>
</div>
</div>
</div>
@@ -346,7 +333,6 @@
lang={script.language}
{initialArgs}
{kind}
tour
/>
{:else if step === 3}
<CenteredPage>
@@ -357,5 +343,3 @@
{:else}
Script Builder not available to operators
{/if}
<svelte:window on:keydown={onKeyDown} />

View File

@@ -17,7 +17,6 @@
import { Button, Kbd } from './common'
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
import WindmillIcon from './icons/WindmillIcon.svelte'
import { Tour } from './tutorial'
// Exported
export let schema: Schema = emptySchema()
@@ -28,7 +27,6 @@
export let initialArgs: Record<string, any> = {}
export let fixedOverflowWidgets = true
export let noSyncFromGithub = false
export let tour = false
let websocketAlive = { pyright: false, black: false, deno: false, go: false }
@@ -116,10 +114,6 @@
<svelte:window on:keydown={onKeyDown} />
{#if tour}
<Tour tutorial="script" />
{/if}
<div class="border-b-2 shadow-sm px-1 pr-4" bind:clientWidth={width}>
<div class="flex justify-between space-x-2">
<EditorBar
@@ -152,7 +146,6 @@
<Splitpanes class="!overflow-visible">
<Pane size={60} minSize={10} class="!overflow-visible">
<div
id="script-tutorial-1"
class="pl-2 h-full !overflow-visible"
on:mouseleave={() => {
inferSchema(code)
@@ -187,7 +180,7 @@
</Pane>
<Pane size={40} minSize={10}>
<div class="flex flex-col h-full">
<div id="script-tutorial-4" class="px-2 w-full border-b py-1">
<div class="px-2 w-full border-b py-1">
{#if testIsLoading}
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red" size="xs">
<WindmillIcon
@@ -218,7 +211,7 @@
</div>
<Splitpanes horizontal class="!max-h-[calc(100%-43px)]">
<Pane size={33}>
<div id="script-tutorial-2" class="px-2">
<div class="px-2">
<div class="break-words relative font-sans">
<SchemaForm compact {schema} bind:args bind:isValid />
</div>

View File

@@ -11,7 +11,6 @@
import { Button, Drawer, DrawerContent } from './common'
import HighlightCode from './HighlightCode.svelte'
import FlowPathViewer from './flows/content/FlowPathViewer.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
export let initialPath: string | undefined = undefined
export let scriptPath: string | undefined = undefined
@@ -88,8 +87,6 @@
bind:justValue={scriptPath}
{items}
placeholder="Pick a {itemKind}"
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
/>
{/if}

View File

@@ -110,10 +110,6 @@
}
}
export function focus() {
editor?.focus()
}
let width = 0
async function loadMonaco() {
model = meditor.createModel(code, lang, mUri.parse(uri))
@@ -209,7 +205,7 @@
})
</script>
<div bind:this={divEl} class="{$$props.class ?? ''} editor" bind:clientWidth={width} />
<div bind:this={divEl} class="{$$props.class} editor" bind:clientWidth={width} />
<style>
.editor {

View File

@@ -12,5 +12,5 @@
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
>
{#if view}
<div class="my-4 px-2" transition:slide|local><slot /></div>
<div class="my-4 px-2" transition:slide><slot /></div>
{/if}

View File

@@ -596,7 +596,7 @@
<div
bind:this={divEl}
style="height: 18px;"
class="{$$props.class ?? ''} template rounded-lg min-h-4 mx-0.5 overflow-clip"
class="{$$props.class} template rounded-lg min-h-4 mx-0.5"
bind:clientWidth={width}
/>

View File

@@ -186,9 +186,9 @@
syncIteration++
await loadTestJob(id)
let nextIteration = 50
if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) {
if (syncIteration == ITERATIONS_BEFORE_SLOW_REFRESH) {
nextIteration = 500
} else if (syncIteration > ITERATIONS_BEFORE_SUPER_SLOW_REFRESH) {
} else if (syncIteration == ITERATIONS_BEFORE_SUPER_SLOW_REFRESH) {
nextIteration = 2000
}
setTimeout(() => syncer(id), nextIteration)

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { twMerge } from 'tailwind-merge'
export let options: {
left?: string
@@ -8,15 +7,13 @@
} = {}
export let checked: boolean = false
export let disabled = false
export let textClass = ''
export let textStyle = ''
export let size: 'sm' | 'xs' = 'sm'
const id = (Math.random() + 1).toString(36).substring(10)
const dispatch = createEventDispatcher()
</script>
<span class="{$$props.class ?? ''} z-auto">
<span class="{$$props.class} z-auto">
<label
for={id}
class="inline-flex items-center mt-2 duration-200 {disabled
@@ -25,13 +22,7 @@
>
{#if Boolean(options?.left)}
<span
class={twMerge(
'ml-2 font-medium duration-200',
disabled ? 'text-gray-500' : 'text-gray-900',
size === 'xs' ? 'text-xs' : 'text-sm',
textClass
)}
style={textStyle}
class="mr-2 text-sm font-medium duration-200 {disabled ? 'text-gray-600' : 'text-gray-900'}"
>
{options?.left}
</span>
@@ -60,13 +51,9 @@
</div>
{#if Boolean(options?.right)}
<span
class={twMerge(
'ml-2 font-medium duration-200',
disabled ? 'text-gray-500' : 'text-gray-900',
size === 'xs' ? 'text-xs' : 'text-sm',
textClass
)}
style={textStyle}
class="ml-2 text-sm font-medium duration-200
{disabled ? 'text-gray-500' : 'text-gray-900'}
{size === 'xs' ? 'text-xs' : 'text-sm'}"
>
{options?.right}
</span>

View File

@@ -13,7 +13,7 @@
<Popover notClickable {placement} class={wrapperClass}>
<Icon
class="{light
? 'text-gray-400'
? 'text-gray-300'
: ' text-gray-500'} font-thin inline-block align-middle w-4 {$$props.class}"
data={faInfoCircle}
{scale}

View File

@@ -39,7 +39,6 @@
export function closeDrawer() {
drawer?.closeDrawer()
const index = $page.url.href.lastIndexOf('#')
if (index === -1) return
const hashRemoved = $page.url.href.slice(0, index)
goto(hashRemoved)
}

View File

@@ -3,14 +3,13 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { loadIcon } from '../icon'
import { twMerge } from 'tailwind-merge'
import { goto } from '$app/navigation'
export let id: string
export let componentInput: AppInput | undefined
@@ -22,11 +21,10 @@
export let noWFull = false
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { runnableComponents, worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
const { runnableComponents, worldStore, app } = getContext<AppEditorContext>('AppEditorContext')
let labelValue: string
let color: ButtonType.Color
@@ -34,8 +32,7 @@
let runnableComponent: RunnableComponent
let disabled: boolean | undefined = undefined
let fillContainer: boolean | undefined = undefined
let gotoUrl: string | undefined = undefined
let gotoNewTab: boolean | undefined = undefined
let goto: string | undefined = undefined
let isLoading: boolean = false
let ownClick: boolean = false
@@ -87,37 +84,10 @@
$: errorsMessage = Object.values(errors)
.filter((x) => x != '')
.join('\n')
async function handleClick(event: CustomEvent) {
event?.stopPropagation()
event?.preventDefault()
if (preclickAction) {
await preclickAction()
}
ownClick = true
if (!runnableComponent) {
if (gotoUrl) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(gotoUrl)
}
}
} else {
await runnableComponent?.runComponent()
}
if (recomputeIds) {
await Promise.all(recomputeIds.map((id) => $runnableComponents?.[id]?.()))
}
}
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
<InputValue {id} input={configuration.goto} bind:value={goto} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue {id} input={configuration.beforeIcon} bind:value={beforeIcon} />
@@ -131,18 +101,15 @@
bind:error={errors.disabled}
/>
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
<RunnableWrapper
flexWrap
bind:runnableComponent
{componentInput}
bind:componentInput
{id}
{extraQueryParams}
autoRefresh={false}
goto={gotoUrl}
{gotoNewTab}
{render}
{goto}
>
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
{#if errorsMessage}
@@ -160,17 +127,31 @@
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={handleClick}
on:click={async (e) => {
if (preclickAction) {
await preclickAction()
}
e?.stopPropagation()
e?.preventDefault()
ownClick = true
await runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
}}
{size}
{color}
{loading}
>
<span class="truncate inline-flex gap-2 items-center">
{#if beforeIcon && beforeIconComponent}
{#if beforeIconComponent}
<svelte:component this={beforeIconComponent} size={14} />
{/if}
<div>{labelValue}</div>
{#if afterIcon && afterIconComponent}
{#if afterIconComponent}
<svelte:component this={afterIconComponent} size={14} />
{/if}
</span>

View File

@@ -5,8 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import type { AppEditorContext } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -18,13 +17,10 @@
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'button'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app, runnableComponents, worldStore, stateId } =
getContext<AppViewerContext>('AppViewerContext')
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -35,8 +31,7 @@
let isLoading: boolean = false
$: noInputs =
$stateId != undefined &&
(componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0)
componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0
$: outputs = $worldStore?.outputsById[id] as {
result: Output<Array<any>>
@@ -52,8 +47,6 @@
isLoading = value
}
})
$: css = concatCustomCss($app.css?.formcomponent, customCss)
</script>
<InputValue {id} input={configuration.goto} bind:value={goto} />
@@ -62,22 +55,18 @@
<InputValue {id} input={configuration.size} bind:value={size} />
<RunnableWrapper
{render}
defaultUserInput
bind:runnableComponent
{componentInput}
bind:componentInput
{id}
{goto}
{extraQueryParams}
autoRefresh={false}
forceSchemaDisplay={true}
runnableClass="!block"
runnableStyle={css?.container.style}
>
<AlignWrapper {horizontalAlignment}>
<div
class="flex flex-col gap-2 px-4 w-full {css?.container?.class ?? ''}"
style={css?.container?.style ?? ''}
>
<div class="flex flex-col gap-2 px-4 w-full">
<div>
{#if noInputs}
<div class="text-gray-600 italic text-sm my-4">
@@ -95,8 +84,7 @@
{#if !noInputs}
<Button
loading={isLoading}
btnClasses="my-1 {css?.button?.class ?? ''}"
style={css?.button?.style ?? ''}
btnClasses="my-1"
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))

View File

@@ -5,14 +5,13 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppEditorContext } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import Portal from 'svelte-portal'
import Modal from '$lib/components/common/modal/Modal.svelte'
import { concatCustomCss } from '../../utils'
export let id: string
export let componentInput: AppInput | undefined
@@ -21,12 +20,10 @@
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'button' | 'popup'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app, runnableComponents, worldStore } = getContext<AppViewerContext>('AppViewerContext')
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -38,8 +35,6 @@
let ownClick: boolean = false
let errors: Record<string, string> = {}
let open: boolean = false
$: errorsMessage = Object.values(errors)
.filter((x) => x != '')
.join('\n')
@@ -67,7 +62,7 @@
$: loading = isLoading && ownClick
$: css = concatCustomCss($app?.css?.formbuttoncomponent, customCss)
let open: boolean = false
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
@@ -84,8 +79,6 @@
<Modal
{open}
title={labelValue}
class={css?.popup.class}
style={css?.popup.style}
on:canceled={() => {
open = false
}}
@@ -94,9 +87,9 @@
}}
>
<RunnableWrapper
{render}
defaultUserInput
bind:runnableComponent
{componentInput}
bind:componentInput
{id}
{extraQueryParams}
autoRefresh={false}
@@ -155,8 +148,6 @@
{disabled}
{size}
{color}
btnClasses={css?.button?.class ?? ''}
style={css?.button?.style ?? ''}
on:click={(e) => {
open = true
}}

View File

@@ -15,19 +15,13 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -83,21 +77,17 @@
}
]
}
$: css = concatCustomCss($app.css?.barchartcomponent, customCss)
</script>
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.line} bind:value={lineChart} />
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
{#if lineChart}
<Line {data} options={lineOptions} />
{:else}
<Bar {data} options={barOptions} />
{/if}
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
{#if result}
{#if lineChart}
<Line {data} options={lineOptions} />
{:else}
<Bar {data} options={barOptions} />
{/if}
</div>
{/if}
</RunnableWrapper>

View File

@@ -3,43 +3,34 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import {
IS_APP_PUBLIC_CONTEXT_KEY,
type AppViewerContext,
type ComponentCustomCSS
} from '../../types'
import { IS_APP_PUBLIC_CONTEXT_KEY, type AppEditorContext, type ComponentCustomCSS } from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'header' | 'container'> | undefined = undefined
export let render: boolean
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
const { app } = getContext<AppViewerContext>('AppViewerContext')
const { app } = getContext<AppEditorContext>('AppEditorContext')
let result: any = undefined
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<div
class={twMerge(
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
$app.css?.['displaycomponent']?.['header']?.class,
customCss?.header?.class
)}
>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<div class={twMerge(
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
$app.css?.['displaycomponent']?.['header']?.class,
customCss?.header?.class
)}>
Results
</div>
<div
class={twMerge(
'p-2',
$app.css?.['displaycomponent']?.['container']?.class,
customCss?.container?.class
)}
>
<div class={twMerge(
'p-2',
$app.css?.['displaycomponent']?.['container']?.class,
customCss?.container?.class
)}>
<DisplayResult {result} {requireHtmlApproval} />
</div>
</RunnableWrapper>

View File

@@ -1,24 +1,16 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: string | undefined = undefined
let h: number | undefined = undefined
let w: number | undefined = undefined
$: css = concatCustomCss($app.css?.htmlcomponent, customCss)
</script>
<div
@@ -29,20 +21,12 @@
bind:clientHeight={h}
bind:clientWidth={w}
>
<RunnableWrapper
{render}
autoRefresh
flexWrap
{componentInput}
{id}
bind:initializing
bind:result
>
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:initializing bind:result>
{#key result}
<iframe
frameborder="0"
style="height: {h}px; width: {w}px; {css?.container?.style ?? ''}"
class="p-0 {css?.container?.class ?? ''}"
style="height: {h}px; width: {w}px"
class="p-0"
title="sandbox"
srcdoc={result
? '<scr' + `ipt type="application/javascript" src="/tailwind.js"></script>` + result

View File

@@ -1,9 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { AlignWrapper, InputValue } from '../helpers'
import { AlignWrapper, InputValue, RunnableWrapper } from '../helpers'
import { loadIcon } from '../icon'
export let id: string
@@ -11,10 +8,6 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = []
export let customCss: ComponentCustomCSS<'container' | 'icon'> | undefined = undefined
export let render: boolean
const { app } = getContext<AppViewerContext>('AppViewerContext')
let icon: string | undefined = undefined
let size: number
@@ -22,13 +15,13 @@
let strokeWidth: number
let iconComponent: any
$: handleIcon(icon)
$: icon && handleIcon()
async function handleIcon(i?: string) {
iconComponent = i ? await loadIcon(i) : undefined
async function handleIcon() {
if (icon) {
iconComponent = await loadIcon(icon)
}
}
$: css = concatCustomCss($app.css?.iconcomponent, customCss)
</script>
<InputValue {id} input={configuration.icon} bind:value={icon} />
@@ -36,21 +29,13 @@
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.strokeWidth} bind:value={strokeWidth} />
<AlignWrapper
{render}
{horizontalAlignment}
{verticalAlignment}
class={css?.container?.class ?? ''}
style={css?.container?.style ?? ''}
>
{#if icon && iconComponent}
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if iconComponent}
<svelte:component
this={iconComponent}
size={size || 24}
color={color || 'currentColor'}
strokeWidth={strokeWidth || 2}
class={css?.icon?.class ?? ''}
style={css?.icon?.style ?? ''}
/>
{/if}
</AlignWrapper>

View File

@@ -1,10 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
type FitOption = (typeof staticValues)['objectFitOptions'][number]
@@ -12,10 +8,7 @@
export let id: string
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['loading']
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
export let render: boolean
const { app } = getContext<AppViewerContext>('AppViewerContext')
const fit: Record<FitOption, string> = {
cover: 'object-cover',
contain: 'object-contain',
@@ -25,20 +18,18 @@
let source: string | undefined = undefined
let imageFit: FitOption | undefined = undefined
let altText: string | undefined = undefined
$: css = concatCustomCss($app.css?.imagecomponent, customCss)
let customStyles: string | undefined = undefined
</script>
<InputValue {id} input={configuration.source} bind:value={source} />
<InputValue {id} input={configuration.imageFit} bind:value={imageFit} />
<InputValue {id} input={configuration.altText} bind:value={altText} />
<InputValue {id} input={configuration.customStyles} bind:value={customStyles} />
{#if render}
<img
on:pointerdown|preventDefault
src={source}
alt={altText}
style={css?.image?.style ?? ''}
class={twMerge(`w-full h-full ${fit[imageFit || 'cover']}`, css?.image?.class ?? '')}
/>
{/if}
<img
on:pointerdown|preventDefault
src={source}
alt={altText}
style={customStyles}
class="w-full h-full {fit[imageFit || 'cover']}"
/>

View File

@@ -1,225 +0,0 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import { concatCustomCss } from '../../utils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { InputValue } from '../helpers'
import { twMerge } from 'tailwind-merge'
import { Map, View, Feature } from 'ol'
import { useGeographic } from 'ol/proj'
import { OSM, Vector as VectorSource } from 'ol/source'
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer'
import { Point } from 'ol/geom'
import { defaults as defaultControls } from 'ol/control'
import { findGridItem } from '../../editor/appUtils'
import type { Output } from '../../rx'
interface Marker {
lon: number
lat: number
title?: string
radius?: number
color?: string
strokeWidth?: number
strokeColor?: string
}
const LAYER_NAME = {
MARKER: 'Marker'
} as const
export let id: string
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['mapRegion']
export let customCss: ComponentCustomCSS<'map'> | undefined = undefined
export let render: boolean
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
getContext<AppViewerContext>('AppViewerContext')
$: outputs = $worldStore?.outputsById[id] as {
mapRegion: Output<{
topLeft: { lat: number; lon: number }
bottomRight: { lat: number; lon: number }
}>
}
let map: Map
let mapElement: HTMLDivElement
let longitude: number | undefined = undefined
let latitude: number | undefined = undefined
let zoom: number | undefined = undefined
// If string, it's a JSON file read as text
let markers: Marker[] | string | undefined = undefined
$: if (map && longitude && latitude) {
map.getView().setCenter([longitude, latitude])
}
$: if (map && zoom) {
map.getView().setZoom(zoom)
}
$: if (map && markers) {
updateMarkers()
}
function selectComponent() {
if (!$connectingInput.opened) {
$selectedComponent = id
$focusedGrid = undefined
}
}
function getLayersByName(name: keyof typeof LAYER_NAME) {
return map
.getLayers()
.getArray()
.filter((l) => l.getProperties().name === LAYER_NAME[name])
}
function getMarkerArray(): Marker[] | undefined {
let array: Marker[] | undefined = undefined
if (typeof markers === 'string') {
try {
array = JSON.parse(markers)
} catch (e) {
return undefined
}
} else {
array = markers
}
return array?.filter((m) => !isNaN(+m.lat) && !isNaN(+m.lon))
}
function createMarkerLayers() {
const markerArray = getMarkerArray()
return markerArray?.map((m) => {
return new VectorLayer({
properties: {
name: LAYER_NAME.MARKER
},
source: new VectorSource({
features: [
new Feature({
geometry: new Point([+m.lon, +m.lat]),
name: m.title
})
]
}),
style: {
'circle-radius': m.radius ?? 7,
'circle-fill-color': m.color ?? '#dc2626',
'circle-stroke-width': m.strokeWidth ?? 3,
'circle-stroke-color': m.strokeColor ?? '#fca5a5'
}
})
})
}
function updateMarkers() {
const layers = getLayersByName('MARKER')
if (layers?.length) {
layers.forEach((l) => map.removeLayer(l))
}
createMarkerLayers()?.forEach((l) => map.addLayer(l))
}
onMount(() => {
useGeographic()
map = new Map({
target: mapElement,
layers: [
new TileLayer({
source: new OSM()
}),
...(createMarkerLayers() || [])
],
view: new View({
center: [longitude ?? 0, latitude ?? 0],
zoom: zoom ?? 2
}),
controls: defaultControls({
attribution: false
})
})
updateRegionOutput()
})
$: css = concatCustomCss($app.css?.mapcomponent, customCss)
function updateRegionOutput() {
if (map) {
let extent = map.getView().calculateExtent(map.getSize())
const [left, bottom, right, top] = extent
if (outputs?.mapRegion) {
outputs.mapRegion.set({
topLeft: { lat: top, lon: left },
bottomRight: { lat: bottom, lon: right }
})
}
}
}
function handleSyncRegion() {
const gridItem = findGridItem($app, id)
if (!map || !gridItem) {
return
}
const z = map.getView().getZoom()
updateRegionOutput()
if (z) {
gridItem.data.configuration.zoom.value = z
}
const center = map.getView().getCenter()
if (!center) {
return
}
if (gridItem) {
gridItem.data.configuration.longitude.value = center[0]
gridItem.data.configuration.latitude.value = center[1]
}
}
</script>
<InputValue {id} input={configuration.longitude} bind:value={longitude} />
<InputValue {id} input={configuration.latitude} bind:value={latitude} />
<InputValue {id} input={configuration.zoom} bind:value={zoom} />
<InputValue {id} input={configuration.markers} bind:value={markers} />
{#if render}
<div class="relative h-full w-full">
<div
on:pointerdown|stopPropagation={selectComponent}
bind:this={mapElement}
class={twMerge(`w-full h-full`, css?.map?.class ?? '')}
style={css?.map?.style ?? ''}
/>
{#if $mode !== 'preview'}
<div
class="absolute bottom-0 left-0 px-1 py-0.5 bg-indigo-500 text-white text-2xs"
on:pointerdown={handleSyncRegion}
>
Set region
</div>
{/if}
</div>
{/if}
<style global lang="postcss">
.ol-overlaycontainer-stopevent {
@apply flex flex-col justify-start items-end;
}
.ol-control button {
@apply w-7 h-7 center-center bg-white border border-gray-300 text-gray-700
rounded mt-1 mr-1 shadow duration-200 hover:bg-gray-100 focus:bg-gray-100
hover:border-gray-500 focus:border-gray-500;
}
</style>

View File

@@ -1,321 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { getDocument, type PDFDocumentProxy, type PDFPageProxy } from 'pdfjs-dist'
import 'pdfjs-dist/build/pdf.worker.entry'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { throttle } from '../../../../utils'
import { Button } from '../../../common'
import { Download, Loader2, MoveHorizontal, ZoomIn, ZoomOut } from 'lucide-svelte'
import { fade } from 'svelte/transition'
import { findGridItem } from '../../editor/appUtils'
export let id: string
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['loading']
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, mode, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let source: string | ArrayBuffer | undefined = undefined
let wrapper: HTMLDivElement | undefined = undefined
let error: string | undefined = undefined
let doc: PDFDocumentProxy | undefined = undefined
let pages: PDFPageProxy[] = []
let zoom: number | undefined = undefined
let controlsWidth: number | undefined = undefined
let controlsHeight: number | undefined = undefined
let pageNumber = 1
$: if (source == '') {
resetDoc()
error = 'Set the "Source" attribute of the PDF component'
}
$: zoom && handleZoom()
$: wrapper && loadDocument(source)
$: wideView = controlsWidth && controlsWidth > 450
async function resetDoc() {
await doc?.destroy()
doc = undefined
}
function handleZoom() {
if (zoom && wrapper) {
try {
renderPdf(false)
} catch (err) {
error = err?.message ?? (typeof err === 'string' ? err : 'Error loading PDF')
}
}
}
async function loadDocument(src: string | ArrayBuffer | undefined) {
if (!src) {
return
}
try {
await resetDoc()
doc = await getDocument(src).promise
pageNumber = 1
await renderPdf(false, false)
error = undefined
} catch (err) {
await resetDoc()
error = err?.message ?? (typeof err === 'string' ? err : 'Error loading PDF')
console.log(err)
}
}
async function renderPdf(scaleToViewport = true, resizing = false) {
if (!(doc && wrapper && zoom)) {
return
}
const scrollPosition = wrapper.scrollTop / wrapper.scrollHeight
if (!resizing) {
pages = []
}
const nextPages: typeof pages = []
const nextChildren: HTMLCanvasElement[] = []
const { width } = wrapper.getBoundingClientRect()
let scale = zoom / 100
if (scaleToViewport) {
const firstViewport = (await doc.getPage(1)).getViewport({ scale: 1 })
// Rounded to the first integer that is a multiple of 10 and is less than the viewport width
zoom = Math.floor((width / firstViewport.width) * 10) * 10
scale = zoom / 100
}
for (let i = 0; i < doc.numPages; i++) {
const canvas = document.createElement('canvas')
const canvasContext = canvas.getContext('2d')
if (!canvasContext) {
console.warn('Could not get canvas context for PDF page ' + i)
continue
}
const page = await doc.getPage(i + 1)
nextPages.push(page)
const viewport = page.getViewport({ scale })
canvas.height = viewport.height
canvas.width = viewport.width
canvas.classList.add('mx-auto', 'my-4', 'shadow-sm')
await page.render({ canvasContext, viewport }).promise
nextChildren.push(canvas)
}
while (wrapper.firstChild) {
wrapper.removeChild(wrapper.firstChild)
}
pages = [...nextPages]
wrapper.append(...nextChildren)
wrapper.scrollTo({
top: scrollPosition * wrapper.scrollHeight
})
}
function scrollToPage(page: number) {
page = pageNumber = minMax(page, 1, pages.length)
const offset = (wrapper?.children.item(page - 1) as HTMLCanvasElement | null)?.offsetTop
if (!offset) {
return
}
// controlsHeight + 2px border + half of the top margin
const padding = (controlsHeight ? controlsHeight + 2 : 0) + 8
wrapper?.scrollTo({
top: offset - padding
})
}
const throttledScroll = throttle(onScroll, 400)
function onScroll() {
if (!wrapper) {
return
}
const THRESHOLD = 50
let scrollPosition = wrapper.scrollTop + THRESHOLD + (controlsHeight ?? 0)
let page = 1
for (let i = 0; i < pages.length; i++) {
const canvas = wrapper.children.item(i) as HTMLCanvasElement | null
if (scrollPosition < (canvas?.offsetTop ?? wrapper.scrollHeight)) {
break
}
page = i + 1
}
pageNumber = page
}
function syncZoomValue() {
const gridItem = findGridItem($app, id)
if (gridItem && gridItem.data.configuration.zoom.value !== zoom) {
gridItem.data.configuration.zoom.value = zoom
}
$app = $app
}
async function downloadPdf() {
if (!doc) {
return
}
const data = await doc.saveDocument()
const url = URL.createObjectURL(new Blob([data.buffer]))
const link = document.createElement('a')
link.href = url
link.download = 'document.pdf'
link.click()
URL.revokeObjectURL(url)
}
function minMax(value: number, min: number, max: number) {
if (value < min) {
return min
} else if (value > max) {
return max
}
return value
}
$: css = concatCustomCss($app.css?.pdfcomponent, customCss)
</script>
<InputValue {id} input={configuration.source} bind:value={source} />
<InputValue {id} input={configuration.zoom} bind:value={zoom} />
{#if render}
<div class="relative w-full h-full bg-gray-100">
{#if source && zoom}
{#if pages?.length}
<div
bind:clientWidth={controlsWidth}
bind:clientHeight={controlsHeight}
class="fixed flex {$mode !== 'preview'
? 'w-[calc(100%-2px)] top-[1px]'
: 'w-full top-0'} {wideView
? 'justify-center gap-14'
: '!justify-between'} overflow-x-auto bg-white border mx-auto py-1"
>
<div class="flex justify-start items-center px-2 text-gray-600 text-sm">
<Button
on:click={() => zoom && (zoom -= 10)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Zoom out"
aria-label="Zoom out"
btnClasses="!rounded-r-none !px-2"
>
<ZoomOut size={16} />
</Button>
{#if wideView}
<Button
on:click={() => (zoom = 100)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Reset zoom"
aria-label="Reset zoom"
btnClasses="!w-[50px] !font-medium !rounded-none !border-l-0 !px-1"
>
{zoom.toFixed(0)}%
</Button>
{/if}
<Button
on:click={() => renderPdf(true, true)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Scale to viewport"
aria-label="Scale to viewport"
btnClasses="!rounded-none !border-l-0 !px-2"
>
<MoveHorizontal size={16} />
</Button>
<Button
on:click={() => zoom && (zoom += 10)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Zoom in"
aria-label="Zoom in"
btnClasses="!rounded-l-none !px-2 !border-l-0"
>
<ZoomIn size={16} />
</Button>
</div>
<div class="center-center px-2 text-gray-600 text-sm">
<input
on:input={({ currentTarget }) => {
scrollToPage(currentTarget.valueAsNumber)
}}
min="1"
max={pages.length}
value={pageNumber}
disabled={!doc}
type="number"
class="!w-[45px] !px-1 !py-0"
/>
<span class="whitespace-nowrap pl-1">
/ {pages.length}
</span>
</div>
<div class="flex justify-end items-center px-2 text-gray-600 text-sm">
<Button
on:click={downloadPdf}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Download PDF"
aria-label="Download PDF"
btnClasses="!font-medium !px-2"
>
{#if wideView}
<span class="mr-1"> Download </span>
{/if}
<Download size={16} />
</Button>
</div>
</div>
{:else}
<div
out:fade={{ duration: 200 }}
class="absolute inset-0 center-center flex-col text-center text-sm bg-white text-gray-600"
>
<Loader2 class="animate-spin mb-2" />
Loading PDF
</div>
{/if}
<div
bind:this={wrapper}
on:scroll={throttledScroll}
class={twMerge('w-full h-full overflow-auto', css?.container?.class ?? '', 'bg-gray-100')}
style="padding-top: {controlsHeight ?? 0}px; {css?.container?.style ?? ''}"
/>
{/if}
{#if $mode !== 'preview' && $selectedComponent === id}
<button
class="fixed z-10 bottom-0 left-0 px-2 py-0.5 bg-indigo-500/90
hover:bg-indigo-500 focus:bg-indigo-500 duration-200 text-white text-2xs"
on:click={() => syncZoomValue()}
>
Sync zoom value
</button>
{/if}
{#if error}
<div
class="absolute inset-0 z-20 center-center
bg-gray-100 text-center text-gray-600 text-sm"
>
{error}
</div>
{/if}
</div>
{/if}

View File

@@ -14,19 +14,13 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -66,21 +60,17 @@
}
]
}
$: css = concatCustomCss($app.css?.piechartcomponent, customCss)
</script>
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
{#if doughnut}
<Doughnut {data} {options} />
{:else}
<Pie {data} {options} />
{/if}
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
{#if result}
{#if doughnut}
<Doughnut {data} {options} />
{:else}
<Pie {data} {options} />
{/if}
</div>
{/if}
</RunnableWrapper>

View File

@@ -17,22 +17,16 @@
import Scatter from 'svelte-chartjs/Scatter.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
let zoomable = false
let pannable = false
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -72,17 +66,13 @@
$: data = {
datasets: result ?? []
} as ChartData<'scatter', (number | Point)[], unknown>
$: css = concatCustomCss($app.css?.scatterchartcomponent, customCss)
</script>
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
<Scatter {data} {options} />
{/if}
</div>
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
{#if result}
<Scatter {data} {options} />
{/if}
</RunnableWrapper>

View File

@@ -8,9 +8,8 @@
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { twMerge } from 'tailwind-merge'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
import ResizeWrapper from '../helpers/ResizeWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
@@ -19,16 +18,14 @@
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
const { app } = getContext<AppViewerContext>('AppViewerContext')
const { app } = getContext<AppEditorContext>('AppEditorContext')
let result: string | undefined = undefined
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
let copyButton: boolean
let fitContent = false
function getComponent() {
switch (style) {
@@ -66,39 +63,36 @@
<InputValue {id} input={configuration.style} bind:value={style} />
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
<InputValue {id} input={configuration.fitContent} bind:value={fitContent} />
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<ResizeWrapper {id} shouldWrap={fitContent}>
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
No text
</div>
{:else}
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
<svelte:element
this={component}
class={twMerge(
'whitespace-pre-wrap',
$app.css?.['textcomponent']?.['text']?.class,
customCss?.text?.class,
classes
)}
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
>
{String(result)}
</svelte:element>
{#if copyButton && result}
<Popover notClickable>
<Button size="xs" btnClasses="!px-2" on:click={() => copyToClipboard(result)}>
<Clipboard size={14} strokeWidth={2} />
</Button>
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
</Popover>
{/if}
</div>
{/if}
</AlignWrapper>
</ResizeWrapper>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
No text
</div>
{:else}
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
<svelte:element
this={component}
class={twMerge(
'whitespace-pre-wrap',
$app.css?.['textcomponent']?.['text']?.class,
customCss?.text?.class,
classes
)}
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
>
{String(result)}
</svelte:element>
{#if copyButton && result}
<Popover notClickable>
<Button size="xs" btnClasses="!px-2" on:click={() => copyToClipboard(result)}>
<Clipboard size={14} strokeWidth={2} />
</Button>
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
</Popover>
{/if}
</div>
{/if}
</AlignWrapper>
</RunnableWrapper>

View File

@@ -20,19 +20,13 @@
import { Scatter } from 'svelte-chartjs'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
import { concatCustomCss } from '../../utils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
let logarithmicScale = false
let zoomable = false
@@ -86,18 +80,14 @@
$: data = {
datasets: result ?? []
} as ChartData<'scatter', (number | Point)[], unknown>
$: css = concatCustomCss($app.css?.timeseriescomponent, customCss)
</script>
<InputValue {id} input={configuration.logarithmicScale} bind:value={logarithmicScale} />
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
<Scatter {data} {options} />
{/if}
</div>
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
{#if result}
<Scatter {data} {options} />
{/if}
</RunnableWrapper>

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
// export let configuration: Record<string, AppInput>
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
@@ -16,17 +16,18 @@
let Plotly
onMount(async () => {
//@ts-ignore
await import('https://cdn.plot.ly/plotly-2.18.0.min.js')
if (divEl) {
//@ts-ignore
await import('https://cdn.plot.ly/plotly-2.18.0.min.js')
Plotly = window['Plotly']
Plotly = window['Plotly']
}
})
let h: number | undefined = undefined
let w: number | undefined = undefined
$: Plotly &&
render &&
result &&
divEl &&
h &&
@@ -40,7 +41,12 @@
</script>
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if !Plotly}
<div class="p-2">
<Loader2 class="animate-spin" />
</div>
{/if}
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
@@ -8,7 +9,6 @@
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
@@ -53,7 +53,12 @@
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if !vegaEmbed}
<div class="p-2">
<Loader2 class="animate-spin" />
</div>
{/if}
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -1,15 +1,12 @@
export { default as AppTable } from './table/AppTable.svelte'
export { default as AppAggridTable } from './table/AppAggridTable.svelte'
export { default as AppBarChart } from './AppBarChart.svelte'
export { default as AppDisplayComponent } from './AppDisplayComponent.svelte'
export { default as AppHtml } from './AppHtml.svelte'
export { default as AppIcon } from './AppIcon.svelte'
export { default as AppImage } from './AppImage.svelte'
export { default as AppMap } from './AppMap.svelte'
export { default as AppPdf } from './AppPdf.svelte'
export { default as AppPieChart } from './AppPieChart.svelte'
export { default as AppScatterChart } from './AppScatterChart.svelte'
export { default as AppText } from './AppText.svelte'
export { default as AppTimeseries } from './AppTimeseries.svelte'
export { default as PlotlyHtml } from './PlotlyHtml.svelte'
export { default as VegaLiteHtml } from './VegaLiteHtml.svelte'
export { default as VegaLiteHtml } from './VegaLiteHtml.svelte'

View File

@@ -5,7 +5,7 @@
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppViewerContext } from '../../../types'
import type { AppEditorContext } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -17,13 +17,16 @@
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'selectedRowIndex']
let result: Record<string, any>[] | undefined = undefined
const { worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
const {
worldStore,
staticOutputs: staticOutputsStore,
selectedComponent
} = getContext<AppEditorContext>('AppEditorContext')
let selectedRowIndex = -1
@@ -62,8 +65,6 @@
let columnDefs: any = undefined
let allEditable: boolean | undefined = undefined
let pagination: boolean | undefined = undefined
let pageSize: number | undefined = 10
function onCellValueChanged(event) {
if (result) {
@@ -78,10 +79,8 @@
<InputValue {id} input={configuration.columnDefs} bind:value={columnDefs} />
<InputValue {id} input={configuration.allEditable} bind:value={allEditable} />
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
<InputValue {id} input={configuration.pageSize} bind:value={pageSize} />
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if Array.isArray(result) && result.every(isObject)}
<div
class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full"
@@ -96,15 +95,11 @@
style:width="{clientWidth}px"
class="ag-theme-alpine"
>
{#key pagination}
<AgGridSvelte
bind:rowData={result}
{columnDefs}
{pagination}
paginationPageSize={pageSize}
defaultColDef={{ flex: 1, editable: allEditable, onCellValueChanged }}
/>
{/key}
<AgGridSvelte
bind:rowData={result}
{columnDefs}
defaultColDef={{ flex: 1, editable: allEditable, onCellValueChanged }}
/>
</div>
</div>
{:else if result != undefined}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppViewerContext, BaseAppComponent, ComponentCustomCSS } from '../../../types'
import type { AppEditorContext, BaseAppComponent } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -14,18 +14,12 @@
import { tableOptions } from './tableOptions'
import Alert from '$lib/components/common/alert/Alert.svelte'
import type { ButtonComponent } from '../../../editor/component'
import { concatCustomCss } from '../../../utils'
import { twMerge } from 'tailwind-merge'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
export let initializing: boolean | undefined = undefined
export let customCss:
| ComponentCustomCSS<'container' | 'tableHeader' | 'tableBody' | 'tableFooter'>
| undefined = undefined
export let render: boolean
export const staticOutputs: string[] = [
'selectedRow',
@@ -57,19 +51,16 @@
let table = createSvelteTable(options)
const {
app,
worldStore,
staticOutputs: staticOutputsStore
} = getContext<AppViewerContext>('AppViewerContext')
const { worldStore, staticOutputs: staticOutputsStore } =
getContext<AppEditorContext>('AppEditorContext')
let selectedRowIndex = -1
function toggleRow(row: Record<string, any>, rowIndex: number, force: boolean = false) {
if (selectedRowIndex !== rowIndex || force) {
function toggleRow(row: Record<string, any>, rowIndex: number) {
if (selectedRowIndex !== rowIndex) {
selectedRowIndex = rowIndex
outputs?.selectedRow.set(row.original, force)
outputs?.selectedRowIndex.set(rowIndex, force)
outputs?.selectedRow.set(row.original)
outputs?.selectedRowIndex.set(rowIndex)
}
}
@@ -139,28 +130,16 @@
function rerender() {
table = createSvelteTable(options)
if (result) {
toggleRow({ original: result[0] }, 0, true)
}
}
$: result && rerender()
$: css = concatCustomCss($app.css?.tablecomponent, customCss)
</script>
<InputValue {id} input={configuration.search} bind:value={search} />
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if Array.isArray(result) && result.every(isObject)}
<div
class={twMerge(
'border border-gray-300 shadow-sm divide-y divide-gray-300 h-full',
css?.container?.class ?? '',
'flex flex-col'
)}
style={css?.container?.style ?? ''}
>
<div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
{#if search !== 'Disabled'}
<div class="px-2 py-1">
<div class="flex items-center">
@@ -173,14 +152,7 @@
<div class="overflow-x-auto flex-1 w-full">
<table class="relative w-full border-b border-b-gray-200">
<thead
class={twMerge(
'bg-gray-50 text-left',
css?.tableHeader?.class ?? '',
'sticky top-0 z-40'
)}
style={css?.tableHeader?.style ?? ''}
>
<thead class="sticky top-0 z-40 bg-gray-50 text-left">
{#each $table.getHeaderGroups() as headerGroup}
<tr class="divide-x">
{#each headerGroup.headers as header}
@@ -206,10 +178,7 @@
</tr>
{/each}
</thead>
<tbody
class={twMerge('divide-y divide-gray-200 bg-white', css?.tableBody?.class ?? '')}
style={css?.tableBody?.style ?? ''}
>
<tbody class="divide-y divide-gray-200 bg-white ">
{#each $table.getRowModel().rows as row, rowIndex (row.id)}
<tr
class={classNames(
@@ -246,30 +215,16 @@
<td class="p-2 " on:click={() => toggleRow(row, rowIndex)}>
<div class="center-center h-full w-full flex-wrap gap-1">
{#each actionButtons as actionButton, actionIndex (actionIndex)}
{#if rowIndex == 0}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{:else}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
/>
{/if}
<AppButton
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{/each}
</div>
</td>
@@ -280,13 +235,7 @@
</table>
</div>
<AppTableFooter
paginationEnabled={pagination}
{result}
{table}
class={css?.tableFooter.class}
style={css?.tableFooter.style}
/>
<AppTableFooter paginationEnabled={pagination} {result} {table} />
</div>
{:else if result != undefined}
<Alert title="Parsing issues" type="error" size="xs">

View File

@@ -4,7 +4,6 @@
import type { Table } from '@tanstack/svelte-table'
import { ChevronLeft, ChevronRight } from 'lucide-svelte'
import type { Readable } from 'svelte/store'
import { twMerge } from 'tailwind-merge'
import { tableOptions } from './tableOptions'
type T = Record<string, any>
@@ -12,9 +11,6 @@
export let result: Array<T>
export let paginationEnabled: boolean = false
export let table: Readable<Table<T>>
let c = ''
export { c as class }
export let style = ''
function downloadResultAsJSON() {
const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(result))
@@ -27,10 +23,7 @@
}
</script>
<div
class={twMerge('px-2 py-1 text-xs gap-2 items-center justify-between', c, 'flex flex-row')}
{style}
>
<div class="px-2 py-1 text-xs flex flex-row gap-2 items-center justify-between">
{#if paginationEnabled && result.length > (tableOptions.initialState?.pagination?.pageSize ?? 25)}
<div class="flex items-center gap-2 flex-row">
<Button

View File

@@ -1,47 +1,41 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge'
import { classNames } from '$lib/utils'
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let noWFull = false
let c = ''
export { c as class }
export let style = ''
export let render: boolean = true
function tailwindHorizontalAlignment(alignment?: HorizontalAlignment) {
if (!alignment) return ''
if(!alignment) return '';
const classes: Record<HorizontalAlignment, string> = {
left: 'justify-start',
center: 'justify-center',
right: 'justify-end'
right: 'justify-end',
}
return classes[alignment]
}
function tailwindVerticalAlignment(alignment?: VerticalAlignment) {
if (!alignment) return ''
if(!alignment) return '';
const classes: Record<VerticalAlignment, string> = {
top: 'items-start',
center: 'items-center',
bottom: 'items-end'
bottom: 'items-end',
}
return classes[alignment]
}
$: classes = twMerge(
$: classes = classNames(
'flex z-auto',
noWFull ? '' : 'w-full',
tailwindHorizontalAlignment(horizontalAlignment),
tailwindVerticalAlignment(verticalAlignment),
verticalAlignment ? 'h-full' : '',
c
$$props.class || ''
)
</script>
{#if render}
<div class={classes} {style}>
<slot />
</div>
{/if}
<div class={classes}>
<slot />
</div>

View File

@@ -1,10 +1,5 @@
<script lang="ts">
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '../../inputType'
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../../inputType'
import type { InlineScript } from '../../types'
import RunnableComponent from './RunnableComponent.svelte'
@@ -19,9 +14,8 @@
</script>
<RunnableComponent
render={false}
{id}
{fields}
bind:fields
bind:result
runnable={{
name,

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