Compare commits
101 Commits
rf/diff3
...
app-previe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4140117b6a | ||
|
|
f20ea37eb0 | ||
|
|
67d8009dcf | ||
|
|
87d31ee63b | ||
|
|
fb2d52f99b | ||
|
|
95ccc9edf8 | ||
|
|
9e4d90ad37 | ||
|
|
2e3271257f | ||
|
|
c638897fdc | ||
|
|
71305e5154 | ||
|
|
9e9f8efb8e | ||
|
|
3e5d09ef0b | ||
|
|
b013996585 | ||
|
|
614fb5022a | ||
|
|
0beadfd1ac | ||
|
|
0a76e3533c | ||
|
|
7648b382ff | ||
|
|
25580c1272 | ||
|
|
e2747e824f | ||
|
|
2060445d20 | ||
|
|
2557e136bd | ||
|
|
200cb69d82 | ||
|
|
9ee261fe1a | ||
|
|
8e563a42f5 | ||
|
|
a999eb2112 | ||
|
|
e5dbe7076c | ||
|
|
94e30052bf | ||
|
|
68add7932e | ||
|
|
2ac51b0af0 | ||
|
|
f3232062c3 | ||
|
|
b11a5a2df6 | ||
|
|
e2c4545240 | ||
|
|
70dd6f759c | ||
|
|
dcfb29fb80 | ||
|
|
94f1aadef2 | ||
|
|
58300eb6ac | ||
|
|
304dea4b74 | ||
|
|
f4fe71e074 | ||
|
|
fd4e18f62f | ||
|
|
e428662481 | ||
|
|
b796aeef7a | ||
|
|
55eb48c553 | ||
|
|
a43139fe53 | ||
|
|
c4463bb029 | ||
|
|
cc6eaaf473 | ||
|
|
ed25d9f186 | ||
|
|
35ea2b27b1 | ||
|
|
2c1e3b3372 | ||
|
|
4101d587de | ||
|
|
e6ff3ab6cc | ||
|
|
8fc6c39129 | ||
|
|
fcb5cf4d41 | ||
|
|
2679386bf8 | ||
|
|
580388ce19 | ||
|
|
4e6e66d7b1 | ||
|
|
f4d79ee263 | ||
|
|
38fb3450c8 | ||
|
|
94b20d2f5e | ||
|
|
1753cb7da6 | ||
|
|
2a75cd250e | ||
|
|
29f3fe2663 | ||
|
|
4c913dc4b6 | ||
|
|
5c40ff4290 | ||
|
|
2bbe112444 | ||
|
|
90a12f6131 | ||
|
|
f3f95fa865 | ||
|
|
26784464a4 | ||
|
|
c96e2351d9 | ||
|
|
ddb4916a2e | ||
|
|
1bb5ed9ae0 | ||
|
|
b5b32f00b3 | ||
|
|
c06311faf8 | ||
|
|
8a639b6e7d | ||
|
|
05f568fb8c | ||
|
|
e515c70e71 | ||
|
|
6adc875610 | ||
|
|
8a0d1158c4 | ||
|
|
ea2ebfa92e | ||
|
|
ba856be10d | ||
|
|
333b873ee9 | ||
|
|
2785b05064 | ||
|
|
a67f10eeb6 | ||
|
|
287b2db22f | ||
|
|
a4e4d188ad | ||
|
|
2244e83b9d | ||
|
|
42d1cd6456 | ||
|
|
4b64e75bd1 | ||
|
|
51a7eaaeb0 | ||
|
|
8589b70ccf | ||
|
|
0bf6f23c9e | ||
|
|
e56869092a | ||
|
|
6b8758f4a5 | ||
|
|
fbc929ba1b | ||
|
|
97602ac6db | ||
|
|
8ee9d67f4f | ||
|
|
4bf6e753f1 | ||
|
|
70eab303bd | ||
|
|
c051ffeb42 | ||
|
|
ebb68e5320 | ||
|
|
04a076f1db | ||
|
|
ebd2e0323e |
4
.github/workflows/docker-image.yml
vendored
4
.github/workflows/docker-image.yml
vendored
@@ -182,7 +182,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push privately
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
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@v3
|
||||
uses: docker/build-push-action@v4
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
context: .
|
||||
|
||||
2
.github/workflows/pypi_on_release.yml
vendored
2
.github/workflows/pypi_on_release.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: "{{defaultContext}}:lsp"
|
||||
push: true
|
||||
|
||||
4
.github/workflows/uffizzi-build.yml
vendored
4
.github/workflows/uffizzi-build.yml
vendored
@@ -25,12 +25,12 @@ jobs:
|
||||
run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
|
||||
tags: type=raw,value=60d
|
||||
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
push: true
|
||||
context: ./
|
||||
|
||||
73
CHANGELOG.md
73
CHANGELOG.md
@@ -1,6 +1,79 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** make cli resilient to systems without openable browsers ([c051ffe](https://github.com/windmill-labs/windmill/commit/c051ffeb42c1cff609f93da7745036ea722e17d4))
|
||||
* **frontend:** Disable move in nested subgrid ([#1238](https://github.com/windmill-labs/windmill/issues/1238)) ([70eab30](https://github.com/windmill-labs/windmill/commit/70eab303bd45111ae198d9b710bfd6f9f59e53b0))
|
||||
* **frontend:** Fix inline scripts list ([#1240](https://github.com/windmill-labs/windmill/issues/1240)) ([97602ac](https://github.com/windmill-labs/windmill/commit/97602ac6db1404d36d160a431ffcea6c0f567a48))
|
||||
* **frontend:** Fix subgrid lock ([#1232](https://github.com/windmill-labs/windmill/issues/1232)) ([8ee9d67](https://github.com/windmill-labs/windmill/commit/8ee9d67f4faa91446338b41c664ef91913eb8b81))
|
||||
|
||||
## [1.70.1](https://github.com/windmill-labs/windmill/compare/v1.70.0...v1.70.1) (2023-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** make cli resilient to systems without openable browsers ([c051ffe](https://github.com/windmill-labs/windmill/commit/c051ffeb42c1cff609f93da7745036ea722e17d4))
|
||||
* **frontend:** Disable move in nested subgrid ([#1238](https://github.com/windmill-labs/windmill/issues/1238)) ([70eab30](https://github.com/windmill-labs/windmill/commit/70eab303bd45111ae198d9b710bfd6f9f59e53b0))
|
||||
* **frontend:** Fix subgrid lock ([#1232](https://github.com/windmill-labs/windmill/issues/1232)) ([8ee9d67](https://github.com/windmill-labs/windmill/commit/8ee9d67f4faa91446338b41c664ef91913eb8b81))
|
||||
|
||||
## [1.70.0](https://github.com/windmill-labs/windmill/compare/v1.69.3...v1.70.0) (2023-02-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **apps:** add ag grid ([b690d80](https://github.com/windmill-labs/windmill/commit/b690d801d4aa5695ee558e81d1ed114074dfcb83))
|
||||
* **frontend:** move to other grid ([#1230](https://github.com/windmill-labs/windmill/issues/1230)) ([104e4ac](https://github.com/windmill-labs/windmill/commit/104e4ac5e790c30e6fb6b27726776693038d4f19))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* app setup and sync now uses 1.69.3 ([d38aff2](https://github.com/windmill-labs/windmill/commit/d38aff2fe228f23eb18c3991392928c064e6aca2))
|
||||
* **frontend:** Fix duplication ([#1237](https://github.com/windmill-labs/windmill/issues/1237)) ([e87f4fc](https://github.com/windmill-labs/windmill/commit/e87f4fc44b847a573f5acafc0348fbcbfcb2258f))
|
||||
* **frontend:** fix graph viewer id assignment ([e1f686d](https://github.com/windmill-labs/windmill/commit/e1f686d8508cfc1f73c43be08facc44217ca8de0))
|
||||
|
||||
## [1.69.3](https://github.com/windmill-labs/windmill/compare/v1.69.2...v1.69.3) (2023-02-24)
|
||||
|
||||
|
||||
|
||||
8
LICENSE
8
LICENSE
@@ -8,5 +8,9 @@ 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/ are Apache 2.0 Licensed.
|
||||
The files under community/ are Apache 2.0 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.
|
||||
|
||||
@@ -284,6 +284,7 @@ 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 |
|
||||
|
||||
245
backend/Cargo.lock
generated
245
backend/Cargo.lock
generated
@@ -129,12 +129,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
|
||||
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -255,9 +254,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.8"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bd379e511536bad07447f899300aa526e9bae8e6f66dc5e5ca45d7587b7c1ec"
|
||||
checksum = "6137c6234afb339e75e764c866e3594900f0211e1315d33779f269bbe2ec6967"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -282,7 +281,7 @@ dependencies = [
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http 0.3.5",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
@@ -324,9 +323,9 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.3"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "better_scoped_tls"
|
||||
@@ -337,15 +336,6 @@ 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"
|
||||
@@ -525,9 +515,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.6"
|
||||
version = "4.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
|
||||
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
@@ -540,9 +530,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.1.0"
|
||||
version = "4.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
|
||||
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
@@ -581,9 +571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b"
|
||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
@@ -619,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.19",
|
||||
"time 0.3.20",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@@ -704,9 +694,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.14"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
|
||||
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -966,9 +956,9 @@ checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.10"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
|
||||
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-iter"
|
||||
@@ -1324,9 +1314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
||||
checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -1678,9 +1668,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
|
||||
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -2048,15 +2038,6 @@ 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"
|
||||
@@ -2103,7 +2084,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2147,27 +2127,6 @@ 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"
|
||||
@@ -2516,9 +2475,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
|
||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"toml_edit",
|
||||
@@ -2566,7 +2525,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"built",
|
||||
@@ -2584,7 +2543,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2598,7 +2557,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"heck",
|
||||
@@ -2620,7 +2579,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
@@ -2798,15 +2757,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.14"
|
||||
@@ -3003,7 +2953,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#f51764f8d0c33e8e3b423e8a80dba00926715360"
|
||||
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -3012,24 +2962,20 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#f51764f8d0c33e8e3b423e8a80dba00926715360"
|
||||
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
|
||||
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#f51764f8d0c33e8e3b423e8a80dba00926715360"
|
||||
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -3044,7 +2990,6 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"rustpython-ast",
|
||||
"rustpython-compiler-core",
|
||||
"thiserror",
|
||||
"tiny-keccak",
|
||||
"unic-emoji-char",
|
||||
"unic-ucd-ident",
|
||||
@@ -3083,9 +3028,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
|
||||
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
@@ -3097,9 +3042,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
|
||||
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3697,9 +3642,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "swc_atoms"
|
||||
version = "0.4.37"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f88175a66f5a7c189e752bda520e148317776ecb22c75adc2c2f24c490834bd0"
|
||||
checksum = "5a172f2e444ae1378286cd27ff2a5cb26eadfd7a77c98ccb0edde8992857be1e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
@@ -3711,9 +3656,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_common"
|
||||
version = "0.29.32"
|
||||
version = "0.29.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc8e0e8109b26be70c82d9709562fc88cbcc09e03c2458221cf216c0088dea2"
|
||||
checksum = "a8a75c46065858a37cdda2c1c6fd056986e3b7752d5ec332e91ce312c6a22749"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ast_node",
|
||||
@@ -3738,9 +3683,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.96.7"
|
||||
version = "0.98.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "621c66e27fbb6cbb6434a4e2b25e439e9a2583cc3419a4a83eba51d16ac0cd7b"
|
||||
checksum = "7b1f6bc913d0f1daf0fa713a64660aed27848e0047ee671ab2206ad602730d1f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"is-macro",
|
||||
@@ -3755,9 +3700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.124.12"
|
||||
version = "0.127.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b3f472b3dfbfd279de364d2b014459a281824b938e243a8739037c445d6b6c"
|
||||
checksum = "3875db58a1515382c670679062133294e5ec88aff08776f5fb6e4f3c09c0f5f9"
|
||||
dependencies = [
|
||||
"either",
|
||||
"enum_kind",
|
||||
@@ -3840,16 +3785,15 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
"rustix",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3926,9 +3870,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2"
|
||||
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
@@ -3944,9 +3888,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c"
|
||||
checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
@@ -3977,9 +3921,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.25.0"
|
||||
version = "1.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
|
||||
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
@@ -3993,7 +3937,7 @@ dependencies = [
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.42.0",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4090,19 +4034,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.5.1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.18.1"
|
||||
version = "0.19.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
|
||||
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"nom8",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4151,25 +4095,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"
|
||||
@@ -4184,6 +4109,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -4318,7 +4244,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
|
||||
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
@@ -4327,7 +4253,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
|
||||
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
@@ -4345,7 +4271,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
|
||||
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4493,9 +4419,11 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_names2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029df4cc8238cefc911704ff8fa210853a0f3bce2694d8f51181dd41ee0f3301"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/youknowone/unicode_names2.git?tag=v0.6.0+character-alias#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
|
||||
dependencies = [
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
@@ -4784,7 +4712,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4811,7 +4739,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4844,13 +4772,13 @@ dependencies = [
|
||||
"sql-builder",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"time 0.3.19",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"tokio-tar",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-cookies",
|
||||
"tower-http 0.4.0",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
@@ -4866,7 +4794,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4881,7 +4809,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4894,7 +4822,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4919,7 +4847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4927,7 +4855,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4941,7 +4869,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4953,7 +4881,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4968,7 +4896,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4982,7 +4910,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -5005,7 +4933,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -5119,6 +5047,15 @@ version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.69.2"
|
||||
version = "1.72.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 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';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE queue ADD COLUMN root_job uuid;
|
||||
ALTER TABLE queue ADD COLUMN leaf_jobs jsonb;
|
||||
@@ -550,21 +550,6 @@
|
||||
},
|
||||
"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": [
|
||||
@@ -820,75 +805,6 @@
|
||||
},
|
||||
"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": [],
|
||||
@@ -1170,6 +1086,20 @@
|
||||
},
|
||||
"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": [],
|
||||
@@ -1183,33 +1113,6 @@
|
||||
},
|
||||
"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": [],
|
||||
@@ -4003,6 +3906,76 @@
|
||||
},
|
||||
"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": [
|
||||
@@ -4113,6 +4086,28 @@
|
||||
},
|
||||
"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": [],
|
||||
@@ -4869,27 +4864,6 @@
|
||||
},
|
||||
"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": [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
use git_version::git_version;
|
||||
use sqlx::{Pool, Postgres};
|
||||
@@ -15,6 +15,7 @@ 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;
|
||||
|
||||
@@ -39,6 +40,11 @@ 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())
|
||||
@@ -60,8 +66,63 @@ 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(([0, 0, 0, 0], port));
|
||||
let addr = SocketAddr::from((server_bind_address, port));
|
||||
|
||||
let server_f = async {
|
||||
if server_mode {
|
||||
@@ -72,60 +133,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
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(),
|
||||
|
||||
@@ -836,6 +836,7 @@ impl RunJob {
|
||||
/* scheduled_for_o */ None,
|
||||
/* schedule_path */ None,
|
||||
/* parent_job */ None,
|
||||
/* root job */ None,
|
||||
/* is_flow_step */ false,
|
||||
/* running */ false,
|
||||
None,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.69.2
|
||||
version: 1.72.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -2538,11 +2538,6 @@ 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
|
||||
@@ -3337,6 +3332,22 @@ 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)
|
||||
|
||||
@@ -670,6 +670,7 @@ async fn execute_component(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
*/
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path},
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{get, post, put},
|
||||
Json, Router,
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use hyper::{HeaderMap, StatusCode};
|
||||
use serde::Deserialize;
|
||||
use windmill_common::{
|
||||
error::{JsonResult, Result},
|
||||
utils::{not_found_if_none, StripPath},
|
||||
@@ -19,6 +20,7 @@ use windmill_common::{
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
jobs::add_include_headers,
|
||||
users::Authed,
|
||||
};
|
||||
|
||||
@@ -83,13 +85,21 @@ 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)>,
|
||||
Json(payload): Json<serde_json::Value>,
|
||||
Query(run_query): Query<IncludeHeaderQuery>,
|
||||
headers: HeaderMap,
|
||||
Json(args): Json<Option<serde_json::Map<String, 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
|
||||
@@ -99,7 +109,7 @@ pub async fn update_payload(
|
||||
",
|
||||
&w_id,
|
||||
&path.to_path(),
|
||||
&payload,
|
||||
serde_json::json!(args),
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
@@ -237,6 +237,7 @@ async fn create_flow(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -393,6 +394,7 @@ async fn update_flow(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
|
||||
@@ -102,10 +102,9 @@ pub fn global_service() -> Router {
|
||||
|
||||
async fn get_result_by_id(
|
||||
Extension(db): Extension<DB>,
|
||||
Query(ResultByIdQuery { skip_direct }): Query<ResultByIdQuery>,
|
||||
Path((w_id, flow_id, node_id)): Path<(String, String, String)>,
|
||||
Path((w_id, flow_id, node_id)): Path<(String, Uuid, String)>,
|
||||
) -> windmill_common::error::JsonResult<serde_json::Value> {
|
||||
let res = windmill_queue::get_result_by_id(db, skip_direct, w_id, flow_id, node_id).await?;
|
||||
let res = windmill_queue::get_result_by_id(db, w_id, flow_id, node_id).await?;
|
||||
Ok(Json(res))
|
||||
}
|
||||
|
||||
@@ -177,11 +176,6 @@ 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,
|
||||
@@ -295,28 +289,38 @@ impl RunJobQuery {
|
||||
fn add_include_headers(
|
||||
&self,
|
||||
headers: HeaderMap,
|
||||
mut args: serde_json::Map<String, serde_json::Value>,
|
||||
args: serde_json::Map<String, serde_json::Value>,
|
||||
) -> serde_json::Map<String, serde_json::Value> {
|
||||
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
|
||||
return add_include_headers(&self.include_header, headers, 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>,
|
||||
@@ -1068,6 +1072,8 @@ 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),
|
||||
}
|
||||
@@ -1163,6 +1169,7 @@ pub async fn run_flow_by_path(
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1198,6 +1205,7 @@ pub async fn run_job_by_path(
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1350,6 +1358,7 @@ pub async fn run_wait_result_job_by_path(
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1396,6 +1405,7 @@ pub async fn run_wait_result_job_by_hash(
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1441,6 +1451,7 @@ pub async fn run_wait_result_flow_by_path(
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1503,6 +1514,7 @@ async fn run_preview_job(
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1536,6 +1548,7 @@ async fn run_preview_flow_job(
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1571,6 +1584,7 @@ pub async fn run_job_by_hash(
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
@@ -1775,6 +1789,7 @@ 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))
|
||||
}
|
||||
|
||||
@@ -734,14 +734,10 @@ async fn slack_command(
|
||||
.map_err(|_| error::Error::BadRequest("invalid payload".to_string()))?;
|
||||
|
||||
let body = String::from_utf8_lossy(&body);
|
||||
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()));
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
@@ -785,6 +781,7 @@ async fn slack_command(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
|
||||
@@ -123,6 +123,7 @@ async fn list_scripts(
|
||||
AND workspace_id = ?)"
|
||||
.bind(&w_id),
|
||||
);
|
||||
sqlb.and_where_eq("archived", true);
|
||||
} else {
|
||||
sqlb.and_where_eq("archived", false);
|
||||
}
|
||||
@@ -377,6 +378,7 @@ async fn create_script(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
|
||||
@@ -1311,7 +1311,9 @@ 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": "new_user"}))
|
||||
.json(
|
||||
&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "global_add"}),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::BASE_URL;
|
||||
use crate::{
|
||||
apps::AppWithLastVersion,
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
utils::require_super_admin,
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::response::Redirect;
|
||||
use axum::{
|
||||
body::StreamBody,
|
||||
@@ -30,7 +30,7 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use stripe::CustomerId;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
@@ -63,12 +63,13 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/tarball", get(tarball_workspace))
|
||||
.route("/premium_info", get(premium_info));
|
||||
|
||||
#[cfg(enterprise)]
|
||||
let router = {
|
||||
router
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal));
|
||||
};
|
||||
#[cfg(feature = "enterprise")]
|
||||
tracing::info!("stripe enabled");
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let router = router
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal));
|
||||
|
||||
router
|
||||
}
|
||||
@@ -230,13 +231,13 @@ async fn premium_info(
|
||||
Ok(Json(row))
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Deserialize)]
|
||||
struct PlanQuery {
|
||||
plan: String,
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn stripe_checkout(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
@@ -302,7 +303,7 @@ async fn stripe_checkout(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn stripe_portal(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
@@ -964,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": "new_invite"}))
|
||||
.json(&serde_json::json!({"email" : &nu.email, "event": "workspace_invite"}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
|
||||
@@ -1001,6 +1002,15 @@ 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),
|
||||
|
||||
@@ -125,7 +125,7 @@ pub enum FlowStatusModule {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum JobResult {
|
||||
SingleJob(Uuid),
|
||||
ListJob(Vec<Uuid>),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use reqwest::Client;
|
||||
@@ -152,55 +152,24 @@ pub async fn pull(
|
||||
|
||||
pub async fn get_result_by_id(
|
||||
db: Pool<Postgres>,
|
||||
mut skip_direct: bool,
|
||||
w_id: String,
|
||||
flow_id: String,
|
||||
flow_id: Uuid,
|
||||
node_id: String,
|
||||
) -> error::Result<serde_json::Value> {
|
||||
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 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 result_id = windmill_common::utils::not_found_if_none(
|
||||
result_id,
|
||||
job_result,
|
||||
"Flow result by id",
|
||||
format!("{}, {}", flow_id, node_id),
|
||||
)?;
|
||||
@@ -282,6 +251,7 @@ 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>,
|
||||
@@ -351,7 +321,10 @@ pub async fn push<'c>(
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_super_admin {
|
||||
if usage > MAX_FREE_EXECS {
|
||||
if usage > MAX_FREE_EXECS
|
||||
&& !matches!(job_payload, JobPayload::Dependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::FlowDependencies { .. })
|
||||
{
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
@@ -466,10 +439,10 @@ pub async fn push<'c>(
|
||||
}
|
||||
JobPayload::Flow(flow) => {
|
||||
let value_json = sqlx::query_scalar!(
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
flow,
|
||||
workspace_id
|
||||
)
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
flow,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("not found flow at path {:?}", flow)))?;
|
||||
@@ -531,12 +504,13 @@ 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)
|
||||
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) \
|
||||
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) \
|
||||
RETURNING id",
|
||||
workspace_id,
|
||||
job_id,
|
||||
@@ -559,7 +533,8 @@ pub async fn push<'c>(
|
||||
same_worker,
|
||||
pre_run_error.map(|e| e.to_string()),
|
||||
email,
|
||||
visible_to_owner
|
||||
visible_to_owner,
|
||||
root_job
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
@@ -672,6 +647,10 @@ 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 {
|
||||
|
||||
@@ -85,6 +85,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
Some(next),
|
||||
Some(schedule.path.clone()),
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
|
||||
@@ -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(2000),
|
||||
std::time::Duration::from_millis(3000),
|
||||
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 (>2000ms)"
|
||||
"The expression of evaluation `{expr2}` took too long to execute (>3000ms)"
|
||||
))
|
||||
})??
|
||||
}
|
||||
@@ -145,7 +145,7 @@ fn add_closing_bracket(s: &str) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
const SPLIT_PAT: &str = ";\n";
|
||||
const SPLIT_PAT: &str = ";";
|
||||
async fn eval(
|
||||
context: &mut JsRuntime,
|
||||
expr: &str,
|
||||
@@ -154,14 +154,21 @@ async fn eval(
|
||||
by_id: Option<IdContext>,
|
||||
base_internal_url: &str,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
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 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 (api_code, by_id_code) = if let Some(EvalCreds { workspace, token }) = creds {
|
||||
let by_id_code = if let Some(by_id) = by_id {
|
||||
format!(
|
||||
@@ -198,12 +205,12 @@ const results = new Proxy({{}}, {{
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
let v_str = match v {
|
||||
JobResult::SingleJob(x) => x.to_string(),
|
||||
JobResult::SingleJob(x) => format!("\"{x}\""),
|
||||
JobResult::ListJob(x) => {
|
||||
format!("[{}]", x.iter().map(|x| x.to_string()).join(","))
|
||||
format!("[{}]", x.iter().map(|x| format!("\"{x}\"")).join(","))
|
||||
}
|
||||
};
|
||||
format!("\"{k}\": \"{v_str}\"")
|
||||
format!("\"{k}\": {v_str}")
|
||||
})
|
||||
.join(","),
|
||||
by_id.previous_id,
|
||||
@@ -292,9 +299,8 @@ 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(workspace, &id.parse()?)
|
||||
.get_completed_job_result(workspace, &id.parse()?)
|
||||
.await?
|
||||
.result
|
||||
.clone();
|
||||
Ok(serde_json::json!(result))
|
||||
}
|
||||
@@ -309,7 +315,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, Some(true))
|
||||
.result_by_id(workspace, flow_job_id, node_id)
|
||||
.await
|
||||
.map_or(None, |e| Some(e.into_inner()));
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ lazy_static::lazy_static! {
|
||||
.unwrap();
|
||||
static ref WORKER_UPTIME_OPTS: prometheus::Opts = prometheus::opts!(
|
||||
"worker_uptime",
|
||||
"Total number of milliseconds since the worker has started"
|
||||
"Total number of seconds since the worker has started"
|
||||
);
|
||||
|
||||
static ref TIMEOUT: u16 = std::env::var("TIMEOUT")
|
||||
@@ -445,15 +445,11 @@ pub async fn run_worker(
|
||||
|
||||
insert_initial_ping(worker_instance, &worker_name, ip, db).await;
|
||||
|
||||
let uptime_metric = prometheus::register_int_counter!(WORKER_UPTIME_OPTS
|
||||
let uptime_metric = prometheus::register_counter!(WORKER_UPTIME_OPTS
|
||||
.clone()
|
||||
.const_label("name", &worker_name))
|
||||
.unwrap();
|
||||
uptime_metric.inc_by(
|
||||
((Instant::now() - start_time).as_millis() - uptime_metric.get() as u128)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
|
||||
let worker_execution_duration = prometheus::register_histogram_vec!(
|
||||
prometheus::HistogramOpts::new(
|
||||
@@ -465,6 +461,14 @@ pub async fn run_worker(
|
||||
)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_execution_duration_counter = prometheus::register_counter!(prometheus::opts!(
|
||||
"worker_execution_duration_counter",
|
||||
"Total number of seconds spent executing jobs"
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.expect("register prometheus metric");
|
||||
|
||||
|
||||
let worker_sleep_duration = prometheus::register_histogram!(prometheus::HistogramOpts::new(
|
||||
"worker_sleep_duration",
|
||||
"Duration sleeping waiting for job",
|
||||
@@ -472,6 +476,15 @@ pub async fn run_worker(
|
||||
.const_label("name", &worker_name),)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
|
||||
let worker_sleep_duration_counter = prometheus::register_counter!(prometheus::opts!(
|
||||
"worker_sleep_duration_counter",
|
||||
"Total number of seconds spent sleeping between pulling jobs from the queue"
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.expect("register prometheus metric");
|
||||
|
||||
|
||||
let worker_pull_duration = prometheus::register_histogram!(prometheus::HistogramOpts::new(
|
||||
"worker_pull_duration",
|
||||
"Duration pulling next job",
|
||||
@@ -479,6 +492,13 @@ pub async fn run_worker(
|
||||
.const_label("name", &worker_name),)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_pull_duration_counter = prometheus::register_counter!(prometheus::opts!(
|
||||
"worker_pull_duration_counter",
|
||||
"Total number of seconds spent pulling jobs (if growing large the db is undersized)"
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_execution_failed = prometheus::register_int_counter_vec!(
|
||||
prometheus::Opts::new("worker_execution_failed", "Number of failed jobs",)
|
||||
.const_label("name", &worker_name),
|
||||
@@ -526,11 +546,12 @@ pub async fn run_worker(
|
||||
worker_busy.set(0);
|
||||
|
||||
uptime_metric.inc_by(
|
||||
((Instant::now() - start_time).as_millis() - uptime_metric.get() as u128)
|
||||
(((Instant::now() - start_time).as_millis() as f64)/1000.0 - uptime_metric.get())
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
|
||||
let do_break = async {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_ENV_CHECK {
|
||||
sqlx::query!(
|
||||
@@ -574,7 +595,8 @@ pub async fn run_worker(
|
||||
(job, timer) = {
|
||||
let timer = worker_pull_duration.start_timer();
|
||||
pull(&db, WHITELIST_WORKSPACES.clone(), BLACKLIST_WORKSPACES.clone()).map(|x| (x, timer)) } => {
|
||||
drop(timer);
|
||||
let duration_pull_s = timer.stop_and_record();
|
||||
worker_pull_duration_counter.inc_by(duration_pull_s);
|
||||
(false, job)
|
||||
},
|
||||
}
|
||||
@@ -680,6 +702,8 @@ pub async fn run_worker(
|
||||
.await;
|
||||
};
|
||||
|
||||
let duration = _timer.stop_and_record();
|
||||
worker_execution_duration_counter.inc_by(duration);
|
||||
|
||||
if !*KEEP_JOB_DIR && !(is_flow && same_worker) {
|
||||
let _ = tokio::fs::remove_dir_all(job_dir).await;
|
||||
@@ -689,9 +713,9 @@ pub async fn run_worker(
|
||||
|
||||
let _timer = worker_sleep_duration
|
||||
.start_timer();
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE)).await;
|
||||
|
||||
let duration = _timer.stop_and_record();
|
||||
worker_sleep_duration_counter.inc_by(duration);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(worker = %worker_name, "run_worker: pulling jobs: {}", err);
|
||||
|
||||
@@ -326,6 +326,21 @@ 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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1237,6 +1252,7 @@ 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,
|
||||
@@ -1248,6 +1264,7 @@ 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,
|
||||
|
||||
@@ -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 cache;
|
||||
if (cache) return { isError: false, value: cache };
|
||||
|
||||
if (opts.workspace) {
|
||||
const e = await getWorkspaceByName(opts.workspace);
|
||||
@@ -53,7 +53,6 @@ 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;
|
||||
@@ -62,8 +61,8 @@ export async function resolveWorkspace(
|
||||
|
||||
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
|
||||
if (!token) {
|
||||
token = workspace.token;
|
||||
}
|
||||
@@ -80,9 +79,9 @@ export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo>
|
||||
if (!newToken) {
|
||||
throw new Error("Could not reauth");
|
||||
}
|
||||
removeWorkspace(workspace.name);
|
||||
removeWorkspace(workspace.name, false, opts);
|
||||
workspace.token = newToken;
|
||||
addWorkspace(workspace);
|
||||
addWorkspace(workspace, opts);
|
||||
|
||||
setClient(
|
||||
token,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// windmill
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.66.0/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.66.0/windmill-api/index.ts";
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.69.3/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.69.3/windmill-api/index.ts";
|
||||
|
||||
// cliffy
|
||||
export { Command } from "https://deno.land/x/cliffy@v0.25.7/command/command.ts";
|
||||
@@ -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";
|
||||
|
||||
10
cli/flow.ts
10
cli/flow.ts
@@ -192,7 +192,7 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
|
||||
}
|
||||
async function run(
|
||||
opts: GlobalOptions & {
|
||||
input: string[];
|
||||
data?: string;
|
||||
silent: boolean;
|
||||
},
|
||||
path: string,
|
||||
@@ -200,7 +200,8 @@ async function run(
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
const input = await resolve(opts.input);
|
||||
const input = opts.data ? await resolve(opts.data) : {};
|
||||
|
||||
|
||||
const id = await JobService.runFlowByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
@@ -236,6 +237,7 @@ 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,
|
||||
@@ -257,8 +259,8 @@ const command = new Command()
|
||||
.command("run", "run a flow by path.")
|
||||
.arguments("<path:string>")
|
||||
.option(
|
||||
"-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.",
|
||||
"-d --data <data:string>",
|
||||
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
|
||||
)
|
||||
.option(
|
||||
"-s --silent",
|
||||
|
||||
@@ -51,9 +51,11 @@ export async function browserLogin(
|
||||
const url = `${baseUrl}user/cli?port=${port}`
|
||||
console.log(`Login by going to ${url}`);
|
||||
try {
|
||||
open(url)
|
||||
await open(url)
|
||||
console.log("Opened browser for you");
|
||||
} catch { }
|
||||
} catch {
|
||||
console.error(`Failed to open browser, please navigate to ${url}`)
|
||||
}
|
||||
const firstConnection = await server.accept();
|
||||
const httpFirstConnection = Deno.serveHttp(firstConnection);
|
||||
const firstRequest = (await httpFirstConnection.nextRequest())!;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Command, DenoLandProvider, UpgradeCommand } from "./deps.ts";
|
||||
import { Command, CompletionsCommand, 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.69.2";
|
||||
const VERSION = "v1.72.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
@@ -60,7 +60,8 @@ let command: any = new Command()
|
||||
],
|
||||
provider: new DenoLandProvider({ name: "wmill" }),
|
||||
}),
|
||||
);
|
||||
)
|
||||
.command("completions", new CompletionsCommand());
|
||||
|
||||
if (Number.parseInt(VERSION.replace("v", "").replace(".", "")) > 1700) {
|
||||
command = command
|
||||
|
||||
@@ -11,6 +11,7 @@ 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 {
|
||||
@@ -280,50 +281,27 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
|
||||
.render();
|
||||
}
|
||||
|
||||
export async function resolve(inputs: string[]): Promise<Record<string, any>> {
|
||||
let result = {};
|
||||
|
||||
if (!inputs) {
|
||||
return result;
|
||||
export async function resolve(input: string): Promise<Record<string, any>> {
|
||||
if (!input) {
|
||||
throw new Error("No data given");
|
||||
}
|
||||
|
||||
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 };
|
||||
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
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function run(
|
||||
opts: GlobalOptions & {
|
||||
input: string[];
|
||||
data?: string;
|
||||
silent: boolean;
|
||||
},
|
||||
path: string,
|
||||
@@ -331,7 +309,8 @@ async function run(
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
const input = await resolve(opts.input);
|
||||
|
||||
const input = opts.data ? await resolve(opts.data) : {};
|
||||
const id = await JobService.runScriptByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
@@ -364,7 +343,9 @@ 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 */
|
||||
@@ -403,7 +384,7 @@ export async function track_job(workspace: string, id: string) {
|
||||
}
|
||||
|
||||
if (updates.new_logs) {
|
||||
console.log(updates.new_logs);
|
||||
writeAllSync(Deno.stdout, new TextEncoder().encode(updates.new_logs));
|
||||
logOffset += updates.new_logs.length;
|
||||
}
|
||||
|
||||
@@ -426,12 +407,15 @@ 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");
|
||||
}
|
||||
@@ -466,8 +450,8 @@ const command = new Command()
|
||||
.command("run", "run a script by path")
|
||||
.arguments("<path:string>")
|
||||
.option(
|
||||
"-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.",
|
||||
"-d --data <data:string>",
|
||||
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
|
||||
)
|
||||
.option(
|
||||
"-s --silent",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"lock": "",
|
||||
"path": null,
|
||||
"type": "rawscript",
|
||||
"content": "// import * as wmill from \"https://deno.land/x/windmill@v1.50.0/mod.ts\"\n\nexport async function main(x: string) {\n console.log(\"Hello from Deno! The argument x is \" + x);\n return x;\n}\n",
|
||||
"content": "// import * as wmill from \"https://deno.land/x/windmill@v1.69.3/mod.ts\"\n\nexport async function main(x: string) {\n console.log(\"Hello from Deno! The argument x is \" + x);\n return x;\n}\n",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"x": {
|
||||
|
||||
@@ -9,9 +9,11 @@ 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 {
|
||||
@@ -308,12 +310,18 @@ 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 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>")
|
||||
.arguments("<workspace_name:string:workspace>")
|
||||
.action(switchC as any)
|
||||
.command("add")
|
||||
.description("Add a workspace")
|
||||
@@ -334,6 +342,9 @@ const command = new Command()
|
||||
.command("remove")
|
||||
.description("Remove a workspace")
|
||||
.arguments("<workspace_name:string>")
|
||||
.action(remove as any);
|
||||
.action(remove as any)
|
||||
.command("whoami")
|
||||
.description("Show the currently active user")
|
||||
.action(whoami as any);
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -38,7 +38,9 @@ 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:
|
||||
@@ -61,7 +63,6 @@ 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
|
||||
|
||||
@@ -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 server
|
||||
docker-compose up db windmill_server windmill_worker
|
||||
```
|
||||
|
||||
### 2. Backend is run by cargo
|
||||
@@ -25,9 +25,9 @@ docker-compose up db server
|
||||
**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,7 +36,20 @@ docker-compose up db server
|
||||
|
||||
# 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/`.
|
||||
@@ -56,6 +69,14 @@ 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
|
||||
@@ -82,7 +103,7 @@ sudo caddy run --config ./Caddyfile
|
||||
|
||||
and then go to <http://localhost>
|
||||
|
||||
### Backend is run by remote!
|
||||
### 3. Backend is run by remote!
|
||||
|
||||
```bash
|
||||
sudo caddy run --config ./CaddyfileRemote
|
||||
@@ -119,8 +140,8 @@ Recommended config for VS Code:
|
||||
|
||||
```json
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
```
|
||||
|
||||
- turn _format on save_ on
|
||||
|
||||
2145
frontend/package-lock.json
generated
2145
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.69.2",
|
||||
"version": "1.72.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -15,18 +15,19 @@
|
||||
"test": "playwright test --config=tests-out/playwright.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.29.2",
|
||||
"@playwright/test": "^1.31.1",
|
||||
"@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.4",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
"@windmill-labs/svelte-grid": "^5.1.6",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cssnano": "^5.1.14",
|
||||
@@ -34,8 +35,10 @@
|
||||
"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",
|
||||
@@ -71,6 +74,7 @@
|
||||
"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",
|
||||
@@ -82,7 +86,7 @@
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"svelte-select": "^5.3.1",
|
||||
"tailwind-merge": "^1.9.1",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
|
||||
1
frontend/src/global.d.ts
vendored
1
frontend/src/global.d.ts
vendored
@@ -47,6 +47,7 @@ declare module '@windmill-labs/svelte-grid' {
|
||||
onTopId?: string
|
||||
scroller?: undefined
|
||||
sensor?: number
|
||||
parentWidth?: number
|
||||
}
|
||||
|
||||
export interface Slots<T> {
|
||||
|
||||
@@ -50,12 +50,23 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { json } from 'svelte-highlight/languages'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { truncate } from '$lib/utils'
|
||||
import { Button } from './common'
|
||||
import { copyToClipboard, truncate } from '$lib/utils'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import autosize from 'svelte-autosize'
|
||||
import { ClipboardCopy } from 'lucide-svelte'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
@@ -86,16 +87,36 @@
|
||||
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 <input type="checkbox" bind:checked={forceJson} /></div
|
||||
as JSON <input class="windmillapp" type="checkbox" bind:checked={forceJson} /></div
|
||||
>{/if}{#if typeof result == 'object' && Object.keys(result).length > 0}<div
|
||||
class="mb-2 text-sm text-gray-700"
|
||||
>The result keys are: <b>{truncate(Object.keys(result).join(', '), 50)}</b></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
|
||||
>{/if}{#if !forceJson && resultKind == 'table-col'}<div
|
||||
class="grid grid-flow-col-dense border border-gray-200 rounded-md "
|
||||
>
|
||||
@@ -223,10 +244,8 @@
|
||||
><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>
|
||||
|
||||
@@ -11,17 +11,21 @@
|
||||
| undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="inline-flex flex-row items-center">
|
||||
<div class="inline-flex flex-row items-center truncated">
|
||||
<span class="font-semibold">
|
||||
{label}
|
||||
</span>
|
||||
<Required {required} class="!ml-0" />
|
||||
|
||||
<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
|
||||
>
|
||||
{#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}
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { FlowService, ScheduleService, type Flow } from '$lib/gen'
|
||||
import { FlowService, ScheduleService, type Flow, type FlowModule } from '$lib/gen'
|
||||
import { initHistory, redo, undo } from '$lib/history'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { encodeState, formatCron, loadHubScripts, sendUserToast } from '$lib/utils'
|
||||
import { faCalendarAlt, faEye, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Redo, Undo } from 'lucide-svelte'
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import { Button, ButtonPopup, ButtonPopupItem } from './common'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { OFFSET } from './CronInput.svelte'
|
||||
import FlowGraphViewer from './FlowGraphViewer.svelte'
|
||||
import ScriptEditorDrawer from './flows/content/ScriptEditorDrawer.svelte'
|
||||
import FlowEditor from './flows/FlowEditor.svelte'
|
||||
import { flowStateStore } from './flows/flowState'
|
||||
import { flowStore } from './flows/flowStore'
|
||||
import type { FlowState } from './flows/flowState'
|
||||
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
|
||||
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
|
||||
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
|
||||
@@ -26,8 +26,8 @@
|
||||
export let selectedId: string | undefined
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let loading = false
|
||||
|
||||
let pathError = ''
|
||||
export let flowStore: Writable<Flow>
|
||||
export let flowStateStore: Writable<FlowState>
|
||||
|
||||
async function createSchedule(path: string) {
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
@@ -50,7 +50,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFlow(): Promise<void> {
|
||||
let loadingSave = false
|
||||
|
||||
async function saveFlow(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
const flow = cleanInputs($flowStore)
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
$dirtyStore = false
|
||||
@@ -112,8 +115,12 @@
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
}
|
||||
sendUserToast(`Flow saved at ${$flowStore.path}`)
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
@@ -141,10 +148,15 @@
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const selectedIdStore = writable<string>(selectedId)
|
||||
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
|
||||
|
||||
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)
|
||||
@@ -153,8 +165,13 @@
|
||||
setContext<FlowEditorContext>('FlowEditorContext', {
|
||||
selectedId: selectedIdStore,
|
||||
schedule: scheduleStore,
|
||||
select,
|
||||
previewArgs: previewArgsStore
|
||||
previewArgs: previewArgsStore,
|
||||
scriptEditorDrawer,
|
||||
moving,
|
||||
history,
|
||||
flowStateStore,
|
||||
flowStore,
|
||||
testStepStore
|
||||
})
|
||||
|
||||
async function loadSchedule() {
|
||||
@@ -176,39 +193,47 @@
|
||||
$: initialPath && $workspaceStore && loadSchedule()
|
||||
|
||||
loadHubScripts()
|
||||
|
||||
let flowViewer: Drawer
|
||||
</script>
|
||||
|
||||
{#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">
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<FlowImportExportMenu />
|
||||
<Button
|
||||
btnClasses="inline-flex"
|
||||
startIcon={{ icon: faEye }}
|
||||
variant="border"
|
||||
color="light"
|
||||
size="sm"
|
||||
on:click={flowViewer.openDrawer}
|
||||
>
|
||||
Graph
|
||||
</Button>
|
||||
<div class="flex gap-1">
|
||||
<Button
|
||||
title="Undo"
|
||||
disabled={$history.index == 0}
|
||||
variant="border"
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
$flowStore = undo(history, $flowStore)
|
||||
}}
|
||||
>
|
||||
<Undo size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
title="Redo"
|
||||
disabled={$history.index == $history.history.length - 1}
|
||||
variant="border"
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
$flowStore = redo(history)
|
||||
}}
|
||||
>
|
||||
<Redo size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gap-1 flex-row hidden md:flex shrink overflow-hidden">
|
||||
{#if $scheduleStore.enabled}
|
||||
<Button
|
||||
@@ -255,12 +280,22 @@
|
||||
<div class="flex flex-row space-x-2">
|
||||
<FlowPreviewButtons />
|
||||
<div class="center-center">
|
||||
<Button
|
||||
disabled={pathError != ''}
|
||||
startIcon={{ icon: faSave }}
|
||||
<ButtonPopup
|
||||
loading={loadingSave}
|
||||
size="sm"
|
||||
on:click={saveFlow}>Save</Button
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={() => saveFlow(false)}
|
||||
>
|
||||
<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>
|
||||
|
||||
@@ -79,14 +79,13 @@
|
||||
</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 h-screen"
|
||||
: 'sm:col-span-2 col-span-3'} w-full border border-gray-400 max-h-screen"
|
||||
class:overflow-auto={overflowAuto}
|
||||
>
|
||||
<FlowGraph
|
||||
minHeight={topHeight}
|
||||
minHeight={400}
|
||||
modules={flow?.value?.modules}
|
||||
failureModule={flow?.value?.failure_module}
|
||||
on:click={(e) => (stepDetail = e.detail)}
|
||||
@@ -97,7 +96,9 @@
|
||||
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}
|
||||
<span class="font-black text-lg w-full my-4">
|
||||
<SchemaViewer schema={flow?.schema} />
|
||||
|
||||
<span class="font-black text-lg w-full my-4 mt-14">
|
||||
<span>Click on a step to see its details</span>
|
||||
</span>
|
||||
{:else if stepDetail == 'Input'}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
|
||||
<div
|
||||
class:border={!noBorder}
|
||||
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border-gray-400 h-full"
|
||||
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border-gray-400 h-full max-h-screen"
|
||||
>
|
||||
<div class="bg-white max-h-80 h-full p-1 overflow-auto relative">
|
||||
<div class="bg-white {col ? '' : '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 max-h-80 h-full relative">
|
||||
<div class="overflow-auto {col ? '' : 'max-h-80'} h-full relative">
|
||||
<LogViewer content={logs ?? ''} isLoading={false} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
import { Button, Kbd } from './common'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { dfs, flowStore } from './flows/flowStore'
|
||||
import { dfs } 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'
|
||||
|
||||
@@ -21,11 +20,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 } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const { selectedId, previewArgs, flowStateStore, flowStore } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function sliceModules(modules: FlowModule[], upTo: number, idOrders: string[]): FlowModule[] {
|
||||
@@ -105,7 +104,6 @@
|
||||
|
||||
{#if isRunning}
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
color="red"
|
||||
on:click={async () => {
|
||||
isRunning = false
|
||||
@@ -132,7 +130,6 @@
|
||||
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>
|
||||
@@ -149,14 +146,13 @@
|
||||
</div>
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} />
|
||||
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 ">
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 pr-4">
|
||||
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
class="py-4 max-w-3xl"
|
||||
schema={$flowStore.schema}
|
||||
bind:isValid
|
||||
bind:args={$previewArgs}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
localFlowModuleStates?.[innerModules?.[i - 1]?.id ?? '']?.type ==
|
||||
FlowStatusModule.type.SUCCESS
|
||||
) {
|
||||
localFlowModuleStates[mod.id ?? ''] = { type: mod.type }
|
||||
localFlowModuleStates[mod.id ?? ''] = { type: mod.type, args: job?.args }
|
||||
} else if (
|
||||
mod.type === FlowStatusModule.type.WAITING_FOR_EXECUTOR &&
|
||||
localFlowModuleStates[mod.id ?? '']?.scheduled_for == undefined
|
||||
@@ -109,7 +109,8 @@
|
||||
type: mod.type,
|
||||
scheduled_for: 'scheduled for ' + displayDate(job?.['scheduled_for'], true),
|
||||
job_id: job?.id,
|
||||
parent_module: mod['parent_module']
|
||||
parent_module: mod['parent_module'],
|
||||
args: job?.args
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -329,10 +330,12 @@
|
||||
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,
|
||||
@@ -397,10 +400,12 @@
|
||||
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,
|
||||
@@ -447,13 +452,17 @@
|
||||
<FlowGraph
|
||||
success={isSuccess(job?.['success'])}
|
||||
flowModuleStates={localFlowModuleStates}
|
||||
on:click={(e) => {
|
||||
if (e.detail.id) {
|
||||
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 {
|
||||
selectedNode = e.detail.id
|
||||
} else if (e.detail == 'Result') {
|
||||
selectedNode = 'end'
|
||||
} else if (e.detail == 'Input') {
|
||||
selectedNode = 'start'
|
||||
}
|
||||
}}
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
@@ -497,6 +506,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="px-1 border-b border-black">
|
||||
<JobArgs args={node.args} />
|
||||
</div>
|
||||
|
||||
<FlowJobResult
|
||||
loading={job['running'] == true}
|
||||
noBorder
|
||||
|
||||
@@ -82,16 +82,10 @@
|
||||
}
|
||||
|
||||
function connectProperty(rawValue: string) {
|
||||
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)
|
||||
}
|
||||
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
|
||||
arg.type = 'javascript'
|
||||
propertyType = 'javascript'
|
||||
monaco?.setCode(arg.expr)
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
@@ -154,7 +148,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if !noDynamicToggle}
|
||||
<div class="flex flex-row gap-x-4 gap-y-1 flex-wrap">
|
||||
<div class="flex flex-row gap-x-4 gap-y-1 flex-wrap z-10">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={propertyType}
|
||||
on:selected={(e) => {
|
||||
@@ -196,10 +190,10 @@
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton light position="left" value="static" size="xs">
|
||||
{'${} '}Template <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
|
||||
>Write text or surround javascript with "{openBracket}" and "{closeBracket}". Use
|
||||
`result` to connect to another node's output.
|
||||
</Tooltip></ToggleButton
|
||||
>
|
||||
{:else}
|
||||
@@ -212,9 +206,11 @@
|
||||
value="javascript"
|
||||
startIcon={{ icon: faCode }}
|
||||
size="xs"
|
||||
> <Tooltip
|
||||
>Write javascript expressions directly, using 'flow_input' or 'result'. You can use
|
||||
multiline javascript.
|
||||
</Tooltip></ToggleButton
|
||||
>
|
||||
Dynamic (JS)
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -305,6 +301,4 @@
|
||||
Not recognized input type {argName}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm text-gray-700">Argument at {argName} is undefined</p>
|
||||
{/if}
|
||||
|
||||
114
frontend/src/lib/components/InputTransformSchemaForm.svelte
Normal file
114
frontend/src/lib/components/InputTransformSchemaForm.svelte
Normal file
@@ -0,0 +1,114 @@
|
||||
<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} />
|
||||
@@ -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 type="checkbox" bind:checked={scroll} />
|
||||
<input class="windmillapp" type="checkbox" bind:checked={scroll} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,14 +8,17 @@
|
||||
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
|
||||
@@ -86,6 +89,7 @@
|
||||
detailed={false}
|
||||
topButton
|
||||
bind:args={stepArgs}
|
||||
isFlow={false}
|
||||
/>
|
||||
{#if testIsLoading}
|
||||
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full mt-4" color="red" size="sm">
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
{#if showOptions}
|
||||
<ul
|
||||
class="options"
|
||||
transition:fly={{ duration: 200, y: 5 }}
|
||||
transition:fly|local={{ duration: 200, y: 5 }}
|
||||
on:mousedown|preventDefault={handleOptionMousedown}
|
||||
>
|
||||
{#each filtered as option}
|
||||
|
||||
@@ -277,15 +277,19 @@
|
||||
folderCreated = undefined
|
||||
}}
|
||||
>
|
||||
<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 folder
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if folderCreated}
|
||||
<div class="mt-8" />
|
||||
{#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 folder
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<FolderEditor name={folderCreated} />
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
|
||||
@@ -49,22 +49,11 @@
|
||||
</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}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
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[] = []
|
||||
@@ -59,12 +60,13 @@
|
||||
<div class="flex flex-row gap-x-1 w-full">
|
||||
<Select
|
||||
listAutoWidth={false}
|
||||
--height="34px"
|
||||
value={collection.find((x) => x.value == value)}
|
||||
bind:justValue={value}
|
||||
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 ?? '')}>
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
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)
|
||||
|
||||
@@ -64,9 +65,9 @@
|
||||
let scheduledForStr: string | undefined
|
||||
let invisible_to_owner: false
|
||||
|
||||
$: cliCommand = `wmill ${runnable?.kind} run ${runnable?.path} ${Object.entries(args)
|
||||
.map(([k, v]) => `-i ${k}=${JSON.stringify(v)}`)
|
||||
.join(' ')}`
|
||||
$: cliCommand = `wmill ${isFlow ? 'flow' : 'script'} run ${runnable?.path} -d '${JSON.stringify(
|
||||
args
|
||||
)}'`
|
||||
</script>
|
||||
|
||||
<div class="max-w-6xl">
|
||||
@@ -145,7 +146,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
{#if viewOptions}
|
||||
<div transition:slide class="mt-6">
|
||||
<div transition:slide|local 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">
|
||||
@@ -213,14 +214,14 @@
|
||||
<div class="my-10" />
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
size="xs"
|
||||
endIcon={{ icon: viewCliOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewCliOptions = !viewCliOptions)}
|
||||
>
|
||||
Run it from the CLI
|
||||
</Button>
|
||||
{#if viewCliOptions}
|
||||
<div transition:slide class="mt-2 px-4 pt-2">
|
||||
<div transition:slide|local class="mt-2 px-4 pt-2">
|
||||
<InlineCodeCopy content={cliCommand} />
|
||||
<CliHelpBox />
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,11 @@
|
||||
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[] = []
|
||||
@@ -19,16 +16,12 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -43,17 +36,19 @@
|
||||
}
|
||||
|
||||
function removeExtraKey() {
|
||||
const nargs = {}
|
||||
Object.keys(args ?? {}).forEach((key) => {
|
||||
if (!keys.includes(key)) {
|
||||
delete args[key]
|
||||
delete inputCheck[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 ?? {})
|
||||
@@ -69,48 +64,60 @@
|
||||
<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 !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 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}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -125,11 +132,7 @@
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
if (pickForField) {
|
||||
if (inputTransform) {
|
||||
args[pickForField].value = '$var:' + path
|
||||
} else {
|
||||
args[pickForField] = '$var:' + path
|
||||
}
|
||||
args[pickForField] = '$var:' + path
|
||||
}
|
||||
}}
|
||||
itemName="Variable"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { Button, Kbd } from './common'
|
||||
import { Button, ButtonPopup, ButtonPopupItem, 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'
|
||||
@@ -54,7 +54,9 @@
|
||||
script.content = initialCode(language, kind, template)
|
||||
}
|
||||
|
||||
async function editScript(): Promise<void> {
|
||||
let loadingSave = false
|
||||
async function editScript(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
try {
|
||||
$dirtyStore = false
|
||||
localStorage.removeItem(script.path)
|
||||
@@ -83,12 +85,17 @@
|
||||
kind: script.kind
|
||||
}
|
||||
})
|
||||
sendUserToast(`New script created at hash ${newHash}`)
|
||||
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
|
||||
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
|
||||
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
|
||||
}
|
||||
} catch (error) {
|
||||
sendUserToast(`Impossible to save the script: ${error.body}`, true)
|
||||
sendUserToast(`Impossible to save the script: ${error.body || error.message}`, true)
|
||||
}
|
||||
loadingSave = false
|
||||
}
|
||||
|
||||
async function changeStep(step: number) {
|
||||
@@ -183,14 +190,24 @@
|
||||
>
|
||||
Next {#if step == 1}<Kbd>Enter</Kbd>{/if}
|
||||
</Button>
|
||||
<Button
|
||||
<ButtonPopup
|
||||
loading={loadingSave}
|
||||
size="sm"
|
||||
variant={step == 1 ? 'border' : 'contained'}
|
||||
disabled={step === 1 && pathError !== ''}
|
||||
btnClasses={step == 1 && initialPath == '' ? 'invisible' : ''}
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={editScript}>Save</Button
|
||||
on:click={() => editScript(false)}
|
||||
>
|
||||
<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>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
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
|
||||
@@ -87,6 +88,8 @@
|
||||
bind:justValue={scriptPath}
|
||||
{items}
|
||||
placeholder="Pick a {itemKind}"
|
||||
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
|
||||
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
editor?.focus()
|
||||
}
|
||||
|
||||
let width = 0
|
||||
async function loadMonaco() {
|
||||
model = meditor.createModel(code, lang, mUri.parse(uri))
|
||||
@@ -205,7 +209,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 {
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
|
||||
>
|
||||
{#if view}
|
||||
<div class="my-4 px-2" transition:slide><slot /></div>
|
||||
<div class="my-4 px-2" transition:slide|local><slot /></div>
|
||||
{/if}
|
||||
|
||||
@@ -596,7 +596,7 @@
|
||||
<div
|
||||
bind:this={divEl}
|
||||
style="height: 18px;"
|
||||
class="{$$props.class} template rounded-lg min-h-4 mx-0.5"
|
||||
class="{$$props.class ?? ''} template rounded-lg min-h-4 mx-0.5 overflow-clip"
|
||||
bind:clientWidth={width}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let options: {
|
||||
left?: string
|
||||
@@ -7,6 +8,8 @@
|
||||
} = {}
|
||||
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)
|
||||
@@ -22,7 +25,13 @@
|
||||
>
|
||||
{#if Boolean(options?.left)}
|
||||
<span
|
||||
class="mr-2 text-sm font-medium duration-200 {disabled ? 'text-gray-600' : 'text-gray-900'}"
|
||||
class={twMerge(
|
||||
'ml-2 font-medium duration-200',
|
||||
disabled ? 'text-gray-500' : 'text-gray-900',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
>
|
||||
{options?.left}
|
||||
</span>
|
||||
@@ -51,9 +60,13 @@
|
||||
</div>
|
||||
{#if Boolean(options?.right)}
|
||||
<span
|
||||
class="ml-2 text-sm font-medium duration-200
|
||||
{disabled ? 'text-gray-500' : 'text-gray-900'}
|
||||
{size === 'xs' ? 'text-xs' : 'text-sm'}"
|
||||
class={twMerge(
|
||||
'ml-2 font-medium duration-200',
|
||||
disabled ? 'text-gray-500' : 'text-gray-900',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
>
|
||||
{options?.right}
|
||||
</span>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Popover notClickable {placement} class={wrapperClass}>
|
||||
<Icon
|
||||
class="{light
|
||||
? 'text-gray-300'
|
||||
? 'text-gray-400'
|
||||
: ' text-gray-500'} font-thin inline-block align-middle w-4 {$$props.class}"
|
||||
data={faInfoCircle}
|
||||
{scale}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
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
|
||||
@@ -32,7 +33,8 @@
|
||||
let runnableComponent: RunnableComponent
|
||||
let disabled: boolean | undefined = undefined
|
||||
let fillContainer: boolean | undefined = undefined
|
||||
let goto: string | undefined = undefined
|
||||
let gotoUrl: string | undefined = undefined
|
||||
let gotoNewTab: boolean | undefined = undefined
|
||||
|
||||
let isLoading: boolean = false
|
||||
let ownClick: boolean = false
|
||||
@@ -84,10 +86,37 @@
|
||||
$: 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={goto} />
|
||||
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
<InputValue {id} input={configuration.beforeIcon} bind:value={beforeIcon} />
|
||||
@@ -101,15 +130,17 @@
|
||||
bind:error={errors.disabled}
|
||||
/>
|
||||
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
|
||||
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
|
||||
|
||||
<RunnableWrapper
|
||||
flexWrap
|
||||
bind:runnableComponent
|
||||
bind:componentInput
|
||||
{componentInput}
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
{goto}
|
||||
goto={gotoUrl}
|
||||
{gotoNewTab}
|
||||
>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
{#if errorsMessage}
|
||||
@@ -127,21 +158,7 @@
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={async (e) => {
|
||||
if (preclickAction) {
|
||||
await preclickAction()
|
||||
}
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
ownClick = true
|
||||
await runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
$runnableComponents[id]?.()
|
||||
})
|
||||
}
|
||||
}}
|
||||
on:click={handleClick}
|
||||
{size}
|
||||
{color}
|
||||
{loading}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
@@ -17,10 +18,12 @@
|
||||
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 const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, runnableComponents, worldStore, stateId } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
@@ -31,7 +34,8 @@
|
||||
let isLoading: boolean = false
|
||||
|
||||
$: noInputs =
|
||||
componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0
|
||||
$stateId != undefined &&
|
||||
(componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0)
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<Array<any>>
|
||||
@@ -47,6 +51,8 @@
|
||||
isLoading = value
|
||||
}
|
||||
})
|
||||
|
||||
$: css = concatCustomCss($app.css?.formcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.goto} bind:value={goto} />
|
||||
@@ -55,18 +61,21 @@
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
|
||||
<RunnableWrapper
|
||||
defaultUserInput
|
||||
bind:runnableComponent
|
||||
bind:componentInput
|
||||
{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">
|
||||
<div
|
||||
class="flex flex-col gap-2 px-4 w-full {css?.container?.class ?? ''}"
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
<div>
|
||||
{#if noInputs}
|
||||
<div class="text-gray-600 italic text-sm my-4">
|
||||
@@ -84,7 +93,8 @@
|
||||
{#if !noInputs}
|
||||
<Button
|
||||
loading={isLoading}
|
||||
btnClasses="my-1"
|
||||
btnClasses="my-1 {css?.button?.class ?? ''}"
|
||||
style={css?.button?.style ?? ''}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } 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 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
|
||||
@@ -20,10 +21,11 @@
|
||||
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 const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
@@ -35,6 +37,8 @@
|
||||
let ownClick: boolean = false
|
||||
|
||||
let errors: Record<string, string> = {}
|
||||
let open: boolean = false
|
||||
|
||||
$: errorsMessage = Object.values(errors)
|
||||
.filter((x) => x != '')
|
||||
.join('\n')
|
||||
@@ -62,7 +66,7 @@
|
||||
|
||||
$: loading = isLoading && ownClick
|
||||
|
||||
let open: boolean = false
|
||||
$: css = concatCustomCss($app?.css?.formbuttoncomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
@@ -79,6 +83,8 @@
|
||||
<Modal
|
||||
{open}
|
||||
title={labelValue}
|
||||
class={css?.popup.class}
|
||||
style={css?.popup.style}
|
||||
on:canceled={() => {
|
||||
open = false
|
||||
}}
|
||||
@@ -87,9 +93,8 @@
|
||||
}}
|
||||
>
|
||||
<RunnableWrapper
|
||||
defaultUserInput
|
||||
bind:runnableComponent
|
||||
bind:componentInput
|
||||
{componentInput}
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
@@ -148,6 +153,8 @@
|
||||
{disabled}
|
||||
{size}
|
||||
{color}
|
||||
btnClasses={css?.button?.class ?? ''}
|
||||
style={css?.button?.style ?? ''}
|
||||
on:click={(e) => {
|
||||
open = true
|
||||
}}
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
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 { AppEditorContext, 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 const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -77,17 +82,21 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.barchartcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.line} bind:value={lineChart} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
{#if lineChart}
|
||||
<Line {data} options={lineOptions} />
|
||||
{:else}
|
||||
<Bar {data} options={barOptions} />
|
||||
<RunnableWrapper 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}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type AppEditorContext, 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
|
||||
@@ -18,19 +22,23 @@
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<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
|
||||
)}>
|
||||
<RunnableWrapper 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
|
||||
)}
|
||||
>
|
||||
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>
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext, 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 const staticOutputs: string[] = ['result', 'loading']
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let result: string | undefined = undefined
|
||||
let h: number | undefined = undefined
|
||||
let w: number | undefined = undefined
|
||||
|
||||
$: css = concatCustomCss($app.css?.htmlcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -21,12 +28,12 @@
|
||||
bind:clientHeight={h}
|
||||
bind:clientWidth={w}
|
||||
>
|
||||
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper autoRefresh flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
{#key result}
|
||||
<iframe
|
||||
frameborder="0"
|
||||
style="height: {h}px; width: {w}px"
|
||||
class="p-0"
|
||||
style="height: {h}px; width: {w}px; {css?.container?.style ?? ''}"
|
||||
class="p-0 {css?.container?.class ?? ''}"
|
||||
title="sandbox"
|
||||
srcdoc={result
|
||||
? '<scr' + `ipt type="application/javascript" src="/tailwind.js"></script>` + result
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import { AlignWrapper, InputValue, RunnableWrapper } from '../helpers'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { AlignWrapper, InputValue } from '../helpers'
|
||||
import { loadIcon } from '../icon'
|
||||
|
||||
export let id: string
|
||||
@@ -8,6 +11,9 @@
|
||||
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
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let icon: string | undefined = undefined
|
||||
let size: number
|
||||
@@ -22,6 +28,8 @@
|
||||
iconComponent = await loadIcon(icon)
|
||||
}
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.iconcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.icon} bind:value={icon} />
|
||||
@@ -29,13 +37,20 @@
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.strokeWidth} bind:value={strokeWidth} />
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<AlignWrapper
|
||||
{horizontalAlignment}
|
||||
{verticalAlignment}
|
||||
class={css?.container?.class ?? ''}
|
||||
style={css?.container?.style ?? ''}
|
||||
>
|
||||
{#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>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<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 { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
type FitOption = (typeof staticValues)['objectFitOptions'][number]
|
||||
@@ -8,7 +12,9 @@
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['loading']
|
||||
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const fit: Record<FitOption, string> = {
|
||||
cover: 'object-cover',
|
||||
contain: 'object-contain',
|
||||
@@ -18,18 +24,18 @@
|
||||
let source: string | undefined = undefined
|
||||
let imageFit: FitOption | undefined = undefined
|
||||
let altText: string | undefined = undefined
|
||||
let customStyles: string | undefined = undefined
|
||||
|
||||
$: css = concatCustomCss($app.css?.imagecomponent, customCss)
|
||||
</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} />
|
||||
|
||||
<img
|
||||
on:pointerdown|preventDefault
|
||||
src={source}
|
||||
alt={altText}
|
||||
style={customStyles}
|
||||
class="w-full h-full {fit[imageFit || 'cover']}"
|
||||
style={css?.image?.style ?? ''}
|
||||
class={twMerge(`w-full h-full ${fit[imageFit || 'cover']}`, css?.image?.class ?? '')}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext, 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
|
||||
|
||||
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: 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} />
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
@@ -0,0 +1,318 @@
|
||||
<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 { AppEditorContext, 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
|
||||
|
||||
const { app, mode, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
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} />
|
||||
|
||||
<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>
|
||||
@@ -14,13 +14,18 @@
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { AppEditorContext, 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 const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -60,17 +65,21 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.piechartcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
{#if doughnut}
|
||||
<Doughnut {data} {options} />
|
||||
{:else}
|
||||
<Pie {data} {options} />
|
||||
<RunnableWrapper 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}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -17,16 +17,21 @@
|
||||
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 { AppEditorContext, 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
|
||||
|
||||
let zoomable = false
|
||||
let pannable = false
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -66,13 +71,17 @@
|
||||
$: 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 flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
<RunnableWrapper 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>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
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
|
||||
@@ -26,6 +27,7 @@
|
||||
let result: string | undefined = undefined
|
||||
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
|
||||
let copyButton: boolean
|
||||
let fitContent = false
|
||||
|
||||
function getComponent() {
|
||||
switch (style) {
|
||||
@@ -63,36 +65,39 @@
|
||||
|
||||
<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 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 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>
|
||||
|
||||
@@ -20,13 +20,18 @@
|
||||
import { Scatter } from 'svelte-chartjs'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { ChartOptions, ChartData } from 'chart.js'
|
||||
import type { AppEditorContext, 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 const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let logarithmicScale = false
|
||||
let zoomable = false
|
||||
@@ -80,14 +85,18 @@
|
||||
$: 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 flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
<RunnableWrapper 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>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<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 const staticOutputs: string[] = ['result', 'loading']
|
||||
@@ -41,12 +40,7 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if !Plotly}
|
||||
<div class="p-2">
|
||||
<Loader2 class="animate-spin" />
|
||||
</div>
|
||||
{/if}
|
||||
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
<div on:pointerdown bind:this={divEl} />
|
||||
</RunnableWrapper>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
@@ -53,12 +52,7 @@
|
||||
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if !vegaEmbed}
|
||||
<div class="p-2">
|
||||
<Loader2 class="animate-spin" />
|
||||
</div>
|
||||
{/if}
|
||||
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
<div on:pointerdown bind:this={divEl} />
|
||||
</RunnableWrapper>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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'
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
let columnDefs: any = undefined
|
||||
|
||||
let allEditable: boolean | undefined = undefined
|
||||
let pagination: boolean | undefined = undefined
|
||||
let pageSize: number | undefined = 10
|
||||
|
||||
function onCellValueChanged(event) {
|
||||
if (result) {
|
||||
@@ -79,8 +81,10 @@
|
||||
|
||||
<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 flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap {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"
|
||||
@@ -95,11 +99,15 @@
|
||||
style:width="{clientWidth}px"
|
||||
class="ag-theme-alpine"
|
||||
>
|
||||
<AgGridSvelte
|
||||
bind:rowData={result}
|
||||
{columnDefs}
|
||||
defaultColDef={{ flex: 1, editable: allEditable, onCellValueChanged }}
|
||||
/>
|
||||
{#key pagination}
|
||||
<AgGridSvelte
|
||||
bind:rowData={result}
|
||||
{columnDefs}
|
||||
{pagination}
|
||||
paginationPageSize={pageSize}
|
||||
defaultColDef={{ flex: 1, editable: allEditable, onCellValueChanged }}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
{:else if result != undefined}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { Output } from '../../../rx'
|
||||
import type { AppEditorContext, BaseAppComponent } from '../../../types'
|
||||
import type { AppEditorContext, BaseAppComponent, ComponentCustomCSS } from '../../../types'
|
||||
import InputValue from '../../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
|
||||
@@ -14,12 +14,17 @@
|
||||
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 const staticOutputs: string[] = [
|
||||
'selectedRow',
|
||||
@@ -51,16 +56,19 @@
|
||||
|
||||
let table = createSvelteTable(options)
|
||||
|
||||
const { worldStore, staticOutputs: staticOutputsStore } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
const {
|
||||
app,
|
||||
worldStore,
|
||||
staticOutputs: staticOutputsStore
|
||||
} = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selectedRowIndex = -1
|
||||
|
||||
function toggleRow(row: Record<string, any>, rowIndex: number) {
|
||||
if (selectedRowIndex !== rowIndex) {
|
||||
function toggleRow(row: Record<string, any>, rowIndex: number, force: boolean = false) {
|
||||
if (selectedRowIndex !== rowIndex || force) {
|
||||
selectedRowIndex = rowIndex
|
||||
outputs?.selectedRow.set(row.original)
|
||||
outputs?.selectedRowIndex.set(rowIndex)
|
||||
outputs?.selectedRow.set(row.original, force)
|
||||
outputs?.selectedRowIndex.set(rowIndex, force)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +138,28 @@
|
||||
|
||||
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 flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap {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">
|
||||
<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 ?? ''}
|
||||
>
|
||||
{#if search !== 'Disabled'}
|
||||
<div class="px-2 py-1">
|
||||
<div class="flex items-center">
|
||||
@@ -152,7 +172,14 @@
|
||||
|
||||
<div class="overflow-x-auto flex-1 w-full">
|
||||
<table class="relative w-full border-b border-b-gray-200">
|
||||
<thead class="sticky top-0 z-40 bg-gray-50 text-left">
|
||||
<thead
|
||||
class={twMerge(
|
||||
'bg-gray-50 text-left',
|
||||
css?.tableHeader?.class ?? '',
|
||||
'sticky top-0 z-40'
|
||||
)}
|
||||
style={css?.tableHeader?.style ?? ''}
|
||||
>
|
||||
{#each $table.getHeaderGroups() as headerGroup}
|
||||
<tr class="divide-x">
|
||||
{#each headerGroup.headers as header}
|
||||
@@ -178,7 +205,10 @@
|
||||
</tr>
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white ">
|
||||
<tbody
|
||||
class={twMerge('divide-y divide-gray-200 bg-white', css?.tableBody?.class ?? '')}
|
||||
style={css?.tableBody?.style ?? ''}
|
||||
>
|
||||
{#each $table.getRowModel().rows as row, rowIndex (row.id)}
|
||||
<tr
|
||||
class={classNames(
|
||||
@@ -215,16 +245,28 @@
|
||||
<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)}
|
||||
<AppButton
|
||||
noWFull
|
||||
{...actionButton}
|
||||
preclickAction={async () => {
|
||||
toggleRow(row, rowIndex)
|
||||
}}
|
||||
extraQueryParams={{ row: row.original }}
|
||||
bind:componentInput={actionButton.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
|
||||
/>
|
||||
{#if rowIndex == 0}
|
||||
<AppButton
|
||||
noWFull
|
||||
{...actionButton}
|
||||
preclickAction={async () => {
|
||||
toggleRow(row, rowIndex)
|
||||
}}
|
||||
extraQueryParams={{ row: row.original }}
|
||||
bind:componentInput={actionButton.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
|
||||
/>
|
||||
{:else}
|
||||
<AppButton
|
||||
noWFull
|
||||
{...actionButton}
|
||||
preclickAction={async () => {
|
||||
toggleRow(row, rowIndex)
|
||||
}}
|
||||
extraQueryParams={{ row: row.original }}
|
||||
bind:componentInput={actionButton.componentInput}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</td>
|
||||
@@ -235,7 +277,13 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<AppTableFooter paginationEnabled={pagination} {result} {table} />
|
||||
<AppTableFooter
|
||||
paginationEnabled={pagination}
|
||||
{result}
|
||||
{table}
|
||||
class={css?.tableFooter.class}
|
||||
style={css?.tableFooter.style}
|
||||
/>
|
||||
</div>
|
||||
{:else if result != undefined}
|
||||
<Alert title="Parsing issues" type="error" size="xs">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
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>
|
||||
@@ -11,6 +12,9 @@
|
||||
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))
|
||||
@@ -23,7 +27,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="px-2 py-1 text-xs flex flex-row gap-2 items-center justify-between">
|
||||
<div
|
||||
class={twMerge('px-2 py-1 text-xs gap-2 items-center justify-between', c, 'flex flex-row')}
|
||||
{style}
|
||||
>
|
||||
{#if paginationEnabled && result.length > (tableOptions.initialState?.pagination?.pageSize ?? 25)}
|
||||
<div class="flex items-center gap-2 flex-row">
|
||||
<Button
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
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 = ''
|
||||
|
||||
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 = classNames(
|
||||
$: classes = twMerge(
|
||||
'flex z-auto',
|
||||
noWFull ? '' : 'w-full',
|
||||
tailwindHorizontalAlignment(horizontalAlignment),
|
||||
tailwindVerticalAlignment(verticalAlignment),
|
||||
verticalAlignment ? 'h-full' : '',
|
||||
$$props.class || ''
|
||||
c
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class={classes}>
|
||||
<div class={classes} {style}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<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'
|
||||
|
||||
@@ -15,7 +20,7 @@
|
||||
|
||||
<RunnableComponent
|
||||
{id}
|
||||
bind:fields
|
||||
{fields}
|
||||
bind:result
|
||||
runnable={{
|
||||
name,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
@@ -11,24 +12,51 @@
|
||||
export let value: T
|
||||
export let id: string | undefined = undefined
|
||||
export let error: string = ''
|
||||
|
||||
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
|
||||
|
||||
$: if (input && !deepEqual(input, lastInput)) {
|
||||
lastInput = JSON.parse(JSON.stringify(input))
|
||||
// Needed because of file uploads
|
||||
if (input?.['value'] instanceof ArrayBuffer) {
|
||||
lastInput.value = input?.['value']
|
||||
}
|
||||
}
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: state = $worldStore?.state
|
||||
$: input && $worldStore && handleConnection()
|
||||
$: input && input.type == 'template' && $state && (value = getValue(input))
|
||||
$: input && input.type == 'eval' && $state && (value = evalExpr(input))
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
const debounce_ms = 50
|
||||
function debounce(cb: () => void) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeout = setTimeout(cb, debounce_ms)
|
||||
}
|
||||
|
||||
$: lastInput && $worldStore && debounce(handleConnection)
|
||||
$: lastInput &&
|
||||
lastInput.type == 'template' &&
|
||||
$state &&
|
||||
debounce(() => (value = getValue(lastInput)))
|
||||
$: lastInput &&
|
||||
lastInput.type == 'eval' &&
|
||||
$state &&
|
||||
debounce(() => (value = evalExpr(lastInput)))
|
||||
|
||||
function handleConnection() {
|
||||
if (input.type === 'connected') {
|
||||
$worldStore?.connect<any>(input, onValueChange)
|
||||
} else if (input.type === 'static' || input.type == 'template') {
|
||||
setTimeout(() => (value = getValue(input)), 0)
|
||||
} else if (input.type == 'eval') {
|
||||
setTimeout(() => ((value = evalExpr(input as EvalAppInput)), 0))
|
||||
} else if (input.type == 'upload') {
|
||||
setTimeout(() => ((value = (input as UploadAppInput).value), 0))
|
||||
if (lastInput.type === 'connected') {
|
||||
$worldStore?.connect<any>(lastInput, onValueChange)
|
||||
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
|
||||
value = getValue(lastInput)
|
||||
} else if (lastInput.type == 'eval') {
|
||||
value = evalExpr(lastInput as EvalAppInput)
|
||||
} else if (lastInput.type == 'upload') {
|
||||
value = (lastInput as UploadAppInput).value
|
||||
} else {
|
||||
setTimeout(() => (value = undefined), 0)
|
||||
value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +126,8 @@
|
||||
}
|
||||
|
||||
function onValueChange(newValue: any): void {
|
||||
if (input.type === 'connected' && newValue !== undefined && newValue !== null) {
|
||||
const { connection } = input
|
||||
|
||||
if (lastInput.type === 'connected' && newValue !== undefined && newValue !== null) {
|
||||
const { connection } = lastInput
|
||||
if (!connection) {
|
||||
// No connection
|
||||
return
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { findGridItem } from '../../editor/appUtils'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
|
||||
export let id: string
|
||||
export let shouldWrap: boolean = false
|
||||
const { app, breakpoint } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: gridItem = findGridItem($app, id)
|
||||
|
||||
let wrapper: HTMLElement
|
||||
|
||||
$: {
|
||||
if (wrapper && gridItem && shouldWrap) {
|
||||
const wrapperHeight = wrapper.getBoundingClientRect().height
|
||||
const width = $breakpoint === 'sm' ? 3 : 12
|
||||
gridItem[width].h = Math.ceil(wrapperHeight / 36)
|
||||
gridItem = gridItem
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shouldWrap}
|
||||
<div bind:this={wrapper}>
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
@@ -2,12 +2,13 @@
|
||||
import { goto } from '$app/navigation'
|
||||
import type { Schema } from '$lib/common'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import { AppService, type CompletedJob } from '$lib/gen'
|
||||
import { defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { Bug } from 'lucide-svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { computeFields } from '../../editor/inlineScriptsPanel/utils'
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
@@ -22,11 +23,12 @@
|
||||
export let autoRefresh: boolean = true
|
||||
export let result: any = undefined
|
||||
export let forceSchemaDisplay: boolean = false
|
||||
export let defaultUserInput = false
|
||||
export let flexWrap = false
|
||||
export let wrapperClass = ''
|
||||
export let wrapperStyle = ''
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let gotoUrl: string | undefined = undefined
|
||||
export let gotoNewTab: boolean | undefined = undefined
|
||||
|
||||
const {
|
||||
worldStore,
|
||||
@@ -37,7 +39,8 @@
|
||||
jobs,
|
||||
noBackend,
|
||||
errorByComponent,
|
||||
mode
|
||||
mode,
|
||||
stateId
|
||||
} = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
onMount(() => {
|
||||
@@ -49,12 +52,13 @@
|
||||
}
|
||||
})
|
||||
|
||||
let args: Record<string, any> = {}
|
||||
let args: Record<string, any> | undefined = undefined
|
||||
let testIsLoading = false
|
||||
let runnableInputValues: Record<string, any> = {}
|
||||
let executeTimeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
function setDebouncedExecute() {
|
||||
console.log('EXECUTE')
|
||||
executeTimeout && clearTimeout(executeTimeout)
|
||||
executeTimeout = setTimeout(() => {
|
||||
executeComponent(true)
|
||||
@@ -82,9 +86,8 @@
|
||||
}
|
||||
|
||||
$: fields && (lazyStaticValues = computeStaticValues())
|
||||
$: runnableInputValues &&
|
||||
extraQueryParams &&
|
||||
args &&
|
||||
$: console.log(runnableInputValues, extraQueryParams, args, autoRefresh, testJobLoader)
|
||||
$: (runnableInputValues || extraQueryParams || args) &&
|
||||
autoRefresh &&
|
||||
testJobLoader &&
|
||||
setDebouncedExecute()
|
||||
@@ -103,30 +106,9 @@
|
||||
}
|
||||
|
||||
$: outputs?.loading?.set(testIsLoading)
|
||||
$: schemaStripped = stripSchema(fields, $stateId)
|
||||
|
||||
$: runnable?.type === 'runnableByName' && loadSchemaAndInputsByName()
|
||||
|
||||
async function loadSchemaAndInputsByName() {
|
||||
if (runnable?.type === 'runnableByName') {
|
||||
const { inlineScript } = runnable
|
||||
// Inline scripts directly provide the schema
|
||||
if (inlineScript) {
|
||||
const newSchema = inlineScript.schema
|
||||
|
||||
const newFields = computeFields(newSchema, defaultUserInput, fields)
|
||||
|
||||
if (JSON.stringify(newFields) !== JSON.stringify(fields)) {
|
||||
setTimeout(() => {
|
||||
fields = newFields
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: schemaStripped = runnable && stripSchema(fields)
|
||||
|
||||
function stripSchema(inputs: AppInputs): Schema {
|
||||
function stripSchema(inputs: AppInputs, s: any): Schema {
|
||||
let schema =
|
||||
runnable?.type == 'runnableByName' ? runnable.inlineScript?.schema : runnable?.schema
|
||||
try {
|
||||
@@ -182,7 +164,7 @@
|
||||
if (field?.type == 'static' && fields[k]) {
|
||||
staticRunnableInputs[k] = field.value
|
||||
} else if (field?.type == 'user') {
|
||||
nonStaticRunnableInputs[k] = args[k]
|
||||
nonStaticRunnableInputs[k] = args?.[k]
|
||||
} else {
|
||||
nonStaticRunnableInputs[k] = runnableInputValues[k]
|
||||
}
|
||||
@@ -261,7 +243,13 @@
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
}
|
||||
gotoUrl && gotoUrl != '' && result?.error == undefined && goto(gotoUrl)
|
||||
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
|
||||
if (gotoNewTab) {
|
||||
window.open(gotoUrl, '_blank')
|
||||
} else {
|
||||
goto(gotoUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -270,12 +258,7 @@
|
||||
bind:this={testJobLoader}
|
||||
/>
|
||||
|
||||
<div class="h-full flex relative flex-row flex-wrap {wrapperClass}">
|
||||
{#if !initializing && autoRefresh === true}
|
||||
<div class="flex absolute top-1 right-1">
|
||||
<RefreshButton componentId={id} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="h-full flex relative flex-row flex-wrap {wrapperClass}" style={wrapperStyle}>
|
||||
{#if schemaStripped && Object.keys(schemaStripped?.properties ?? {}).length > 0 && (autoRefresh || forceSchemaDisplay)}
|
||||
<div class="px-2 h-fit min-h-0">
|
||||
<SchemaForm
|
||||
@@ -294,16 +277,28 @@
|
||||
Please select a runnable
|
||||
</Alert>
|
||||
{:else if result?.error && $mode === 'preview'}
|
||||
<div class="p-2">
|
||||
<Alert type="error" title="Error during execution">
|
||||
<div class="flex flex-col gap-2">
|
||||
An error occured, please contact the app author.
|
||||
<span class="font-semibold">Job id: {testJob?.id}</span>
|
||||
<pre class=" whitespace-pre-wrap text-gray-900 bg-white border w-full p-4 text-xs"
|
||||
>{JSON.stringify(result.error, null, 4)}
|
||||
</pre>
|
||||
</div>
|
||||
</Alert>
|
||||
<div
|
||||
title="Error"
|
||||
class={classNames(
|
||||
'text-red-500 px-1 text-2xs py-0.5 font-bold w-fit absolute border border-red-500 -bottom-2 shadow left-1/2 transform -translate-x-1/2 z-50 cursor-pointer',
|
||||
'bg-red-100/80'
|
||||
)}
|
||||
>
|
||||
<Popover notClickable placement="bottom" popupClass="!bg-white border w-96">
|
||||
<Bug size={14} />
|
||||
<span slot="text">
|
||||
<div class="bg-white">
|
||||
<Alert type="error" title="Error during execution">
|
||||
<div class="flex flex-col gap-2">
|
||||
An error occured, please contact the app author.
|
||||
<span class="font-semibold">Job id: {testJob?.id}</span>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</span>
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="block grow w-full max-h-full border border-red-300 bg-red-50 relative">
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
@@ -311,4 +306,9 @@
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
{#if !initializing && autoRefresh === true}
|
||||
<div class="flex absolute top-1 right-1 z-50">
|
||||
<RefreshButton componentId={id} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
export let autoRefresh: boolean = true
|
||||
export let runnableComponent: RunnableComponent | undefined = undefined
|
||||
export let forceSchemaDisplay: boolean = false
|
||||
export let defaultUserInput = false
|
||||
export let flexWrap = false
|
||||
export let runnableClass = ''
|
||||
export let runnableStyle = ''
|
||||
export let goto: string | undefined = undefined
|
||||
export let gotoNewTab: boolean | undefined = undefined
|
||||
|
||||
const { staticExporter, noBackend } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
@@ -43,10 +44,10 @@
|
||||
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
|
||||
<RunnableComponent
|
||||
gotoUrl={goto}
|
||||
{gotoNewTab}
|
||||
{flexWrap}
|
||||
{defaultUserInput}
|
||||
bind:this={runnableComponent}
|
||||
bind:fields={componentInput.fields}
|
||||
fields={componentInput.fields}
|
||||
bind:result
|
||||
runnable={componentInput.runnable}
|
||||
{autoRefresh}
|
||||
@@ -55,6 +56,7 @@
|
||||
{forceSchemaDisplay}
|
||||
{initializing}
|
||||
wrapperClass={runnableClass}
|
||||
wrapperStyle={runnableStyle}
|
||||
>
|
||||
<slot />
|
||||
</RunnableComponent>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
@@ -11,8 +12,9 @@
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
export const staticOutputs: string[] = ['result']
|
||||
|
||||
@@ -27,6 +29,8 @@
|
||||
}
|
||||
|
||||
$: defaultValue != undefined && outputs?.result.set(defaultValue)
|
||||
|
||||
$: css = concatCustomCss($app.css?.checkboxcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
@@ -40,6 +44,8 @@
|
||||
}}
|
||||
checked={defaultValue}
|
||||
options={{ right: labelValue }}
|
||||
textClass={css?.text?.class ?? ''}
|
||||
textStyle={css?.text?.style ?? ''}
|
||||
on:change={(e) => {
|
||||
outputs.result.set(e.detail)
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputDefaultValue from '../helpers/InputDefaultValue.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
@@ -12,8 +14,9 @@
|
||||
export let inputType: 'date' | 'time' | 'datetime-local'
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let input: HTMLInputElement
|
||||
let labelValue: string = 'Title'
|
||||
let minValue: string = ''
|
||||
@@ -29,6 +32,8 @@
|
||||
}
|
||||
|
||||
$: input && handleInput()
|
||||
|
||||
$: css = concatCustomCss($app.css?.dateinputcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
@@ -46,6 +51,7 @@
|
||||
min={minValue}
|
||||
max={maxValue}
|
||||
placeholder="Type..."
|
||||
class="h-full"
|
||||
class={twMerge('mx-0.5', css?.input?.class ?? '')}
|
||||
style={css?.input?.style ?? ''}
|
||||
/>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { FileInput } from '../../../common'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let acceptedFileTypes: string[] | undefined = undefined
|
||||
let allowMultiple: boolean | undefined = undefined
|
||||
@@ -24,6 +27,8 @@
|
||||
async function handleChange(files: string[] | undefined) {
|
||||
outputs?.result.set(files)
|
||||
}
|
||||
|
||||
$: css = concatCustomCss($app.css?.fileinputcomponent, customCss)
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.acceptedFileTypes} bind:value={acceptedFileTypes} />
|
||||
@@ -34,11 +39,12 @@
|
||||
<FileInput
|
||||
accept={acceptedFileTypes?.length ? acceptedFileTypes?.join(', ') : undefined}
|
||||
multiple={allowMultiple}
|
||||
convertToBase64
|
||||
convertTo="base64"
|
||||
on:change={({ detail }) => {
|
||||
handleChange(detail)
|
||||
}}
|
||||
class="w-full h-full"
|
||||
class={twMerge('w-full h-full', css?.container?.class)}
|
||||
style={css?.container?.style}
|
||||
>
|
||||
{text}
|
||||
</FileInput>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user