Compare commits

..

6 Commits

Author SHA1 Message Date
Ádám Kovács
b76426ba83 faet(frontend): Add tour manager store 2023-03-10 20:11:25 +01:00
Ádám Kovács
1cb2a177c6 Merge branch 'main' into tutorials 2023-03-10 19:13:04 +01:00
Ádám Kovács
db87577a1b feat(frontend): Add app editor tour 2023-03-10 19:11:34 +01:00
Ádám Kovács
c219eb0ee9 feat(frontend): Add flow editor tour 2023-03-10 18:36:53 +01:00
Ádám Kovács
bfe5b56c99 feat(frontend): Add script editor tour 2023-03-10 09:38:34 +01:00
Ádám Kovács
4453690521 feat(frontend): Add onboarding tour to home page 2023-03-09 20:11:27 +01:00
272 changed files with 5612 additions and 7336 deletions

View File

@@ -1,91 +1,6 @@
# Changelog
## [1.79.0](https://github.com/windmill-labs/windmill/compare/v1.78.0...v1.79.0) (2023-03-17)
### Features
* **frontend:** add listeners for frontend scripts ([597e38e](https://github.com/windmill-labs/windmill/commit/597e38ef367d38fa97fc443ccb2c721e5964fece))
* **frontend:** add table actions navigation ([#1298](https://github.com/windmill-labs/windmill/issues/1298)) ([c3ba1a6](https://github.com/windmill-labs/windmill/commit/c3ba1a6ab97484a08a5a20187bb858a5af7025cb))
* **frontend:** App component triggers ([#1303](https://github.com/windmill-labs/windmill/issues/1303)) ([078cb1b](https://github.com/windmill-labs/windmill/commit/078cb1bf3e4de08cb018578f04d24392a6462f69))
* **frontend:** Component control ([#1293](https://github.com/windmill-labs/windmill/issues/1293)) ([bd927a2](https://github.com/windmill-labs/windmill/commit/bd927a27ed9581dbf67ea3694f9d989f8d71d2ed))
### Bug Fixes
* **frontend:** App panel styling ([#1284](https://github.com/windmill-labs/windmill/issues/1284)) ([c1dd35c](https://github.com/windmill-labs/windmill/commit/c1dd35c3f0fcbc1be43273f82a873c3c07863417))
* **frontend:** Display app context search on top ([#1300](https://github.com/windmill-labs/windmill/issues/1300)) ([bd3ee81](https://github.com/windmill-labs/windmill/commit/bd3ee81b14846f16ccd16461de99b46fe68be6ba))
* **frontend:** fix horizontal splitpanes ([#1301](https://github.com/windmill-labs/windmill/issues/1301)) ([ea3dab4](https://github.com/windmill-labs/windmill/commit/ea3dab411b3d5dd772e04c8831e789e2470aaf28))
* **frontend:** fix map render ([#1297](https://github.com/windmill-labs/windmill/issues/1297)) ([0092721](https://github.com/windmill-labs/windmill/commit/00927210fd68c31cb793ef4f0efea05711ebcf00))
* **frontend:** Hide archive toggle with empty list ([#1296](https://github.com/windmill-labs/windmill/issues/1296)) ([bac831b](https://github.com/windmill-labs/windmill/commit/bac831b23ce85a683ddbd4537900670a0b7d12a8))
## [1.78.0](https://github.com/windmill-labs/windmill/compare/v1.77.0...v1.78.0) (2023-03-16)
### Features
* **frontend:** app textcomponent editable + tooltip ([11567d6](https://github.com/windmill-labs/windmill/commit/11567d6280ea60f1a8c3c6607c724179775cbbe3))
### Bug Fixes
* **backend:** whitelist for include_header was ignored in some cases ([183a459](https://github.com/windmill-labs/windmill/commit/183a4591df700ab4720de6e92a83631256940089))
* **frontend:** improve rendering performance after component moving ([6f890f2](https://github.com/windmill-labs/windmill/commit/6f890f2120885f90d986fbd655096b45bf9de539))
* **frontend:** remove staticOutputs from apps ([dbdfd62](https://github.com/windmill-labs/windmill/commit/dbdfd626386398180ecba7976714f86365eeccd8))
## [1.77.0](https://github.com/windmill-labs/windmill/compare/v1.76.0...v1.77.0) (2023-03-14)
### Features
* **apps:** state can be used as input in apps ([2f0acb9](https://github.com/windmill-labs/windmill/commit/2f0acb9ffa8dace4a886527dcee49809d019b271))
* **apps:** tabs can be made pages or invisible + better frontend scripts reactivity ([cd645d0](https://github.com/windmill-labs/windmill/commit/cd645d0935f2d06e0ff71f14d2cf63accd378ff3))
* **deno:** add support for custom npm repo ([#1291](https://github.com/windmill-labs/windmill/issues/1291)) ([944795f](https://github.com/windmill-labs/windmill/commit/944795f6eeaa7d01ab1a35a80570a55c363723e6))
* **frontend:** add setTab to frontend scripts ([c2a97c5](https://github.com/windmill-labs/windmill/commit/c2a97c53cfff0fdb35dd8bc249490566eebdc1a9))
* **frontend:** app components output panel ([#1283](https://github.com/windmill-labs/windmill/issues/1283)) ([751edcf](https://github.com/windmill-labs/windmill/commit/751edcf9b8e0976a1d073603c9eff5dc6e714490))
### Bug Fixes
* **backend:** do not cache reference to workspace scripts ([eb73f2a](https://github.com/windmill-labs/windmill/commit/eb73f2a687f6faad301b9038ab8585450bec7481))
* **frontend:** fix app tabs ([#1288](https://github.com/windmill-labs/windmill/issues/1288)) ([c71a577](https://github.com/windmill-labs/windmill/commit/c71a577fead90c9cd01a736b54d859ec4f0b7807))
* **frontend:** fix container deletion ([#1287](https://github.com/windmill-labs/windmill/issues/1287)) ([bc870bd](https://github.com/windmill-labs/windmill/commit/bc870bd03eb76cb8bc0e0c861f6cd8a9c661186b))
* **frontend:** Update setting accordion ([#1285](https://github.com/windmill-labs/windmill/issues/1285)) ([dea12e8](https://github.com/windmill-labs/windmill/commit/dea12e8870ece998bb6607723cbaab9b9a958f22))
## [1.76.0](https://github.com/windmill-labs/windmill/compare/v1.75.0...v1.76.0) (2023-03-13)
### Features
* **frontend:** add frontend (JS) scripts to apps ([f0b1b1f](https://github.com/windmill-labs/windmill/commit/f0b1b1f752731ba434b960a75624118152f53c00))
* **frontend:** Copy, Cut and Paste ([#1279](https://github.com/windmill-labs/windmill/issues/1279)) ([82c139e](https://github.com/windmill-labs/windmill/commit/82c139ed0992be401e250cfb7ecc0fca61b76772))
* **frontend:** disabled for action buttons can now depend on row ([75f87e7](https://github.com/windmill-labs/windmill/commit/75f87e7e1117a9c12afcf626379e94b134a9a493))
* **frontend:** improve drag-n-drop behavior ([cfd489a](https://github.com/windmill-labs/windmill/commit/cfd489a55059e7b6843f99bab261c70b3852e6a2))
### Bug Fixes
* **backend:** improve worker ping api ([c958480](https://github.com/windmill-labs/windmill/commit/c958480ce83844a989f58dd5a70eb288582e2194))
* **frontend:** General fixes and updates ([#1281](https://github.com/windmill-labs/windmill/issues/1281)) ([3e5a179](https://github.com/windmill-labs/windmill/commit/3e5a179eb8cd8001f49c92305141dade1571e20f))
## [1.75.0](https://github.com/windmill-labs/windmill/compare/v1.74.2...v1.75.0) (2023-03-11)
### Features
* add filter jobs by args or result ([3b44f9a](https://github.com/windmill-labs/windmill/commit/3b44f9a72ca0466a44963a4b9657a0ee59b44753))
* **apps:** add resource picker ([8681e83](https://github.com/windmill-labs/windmill/commit/8681e83b574141acbf7e5a389a9e8a4f340336d1))
* **bash:** add default argument handling for bash ([1d5c194](https://github.com/windmill-labs/windmill/commit/1d5c194f09ffba963d52e418c5954843d84ae337))
* **frontend-apps:** add variable picker for static string input on apps ([bc440f8](https://github.com/windmill-labs/windmill/commit/bc440f8d4154ce464c0e027d93b7a0a3b76d782e))
* **frontend:** make runs filters synced with query args ([61a5e1f](https://github.com/windmill-labs/windmill/commit/61a5e1f1accc988628b785b3b9be04c4ea719874))
### Bug Fixes
* **backend:** add killpill for lines reading ([7c825c2](https://github.com/windmill-labs/windmill/commit/7c825c212dd0f1e8be427eabd9a9756303241d1b))
* **cli:** many small fixes ([ce32370](https://github.com/windmill-labs/windmill/commit/ce323709a94d27fb24214719180ea1aafc66d646))
## [1.74.2](https://github.com/windmill-labs/windmill/compare/v1.74.1...v1.74.2) (2023-03-09)

View File

@@ -316,7 +316,6 @@ you to have it being synced automatically everyday.
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
| NPM_CONFIG_REGISTRY | None | Registry to use for NPM dependencies, set if you have a private repository you need to use instead of the default public NPM registry | Worker |
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again | |
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |

307
backend/Cargo.lock generated
View File

@@ -41,17 +41,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
@@ -265,9 +254,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.11"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d8068b6ccb8b34db9de397c7043f91db8b4c66414952c6db944f238c4d3db3"
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
dependencies = [
"async-trait",
"axum-core",
@@ -292,6 +281,7 @@ dependencies = [
"sync_wrapper",
"tokio",
"tower",
"tower-http 0.3.5",
"tower-layer",
"tower-service",
]
@@ -313,6 +303,12 @@ dependencies = [
"tower-service",
]
[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
version = "0.13.1"
@@ -471,7 +467,7 @@ version = "8.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996"
dependencies = [
"semver 1.0.17",
"semver 1.0.16",
"serde",
"toml",
"url",
@@ -494,9 +490,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.24"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -519,9 +515,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.1.9"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9d6ada83c1edcce028902ea27dd929069c70df4c7600b131b4d9a1ad2879cc"
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_derive",
@@ -534,9 +530,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.1.9"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644"
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
dependencies = [
"heck",
"proc-macro-error",
@@ -547,9 +543,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646"
checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
dependencies = [
"os_str_bytes",
]
@@ -800,12 +796,6 @@ dependencies = [
"syn",
]
[[package]]
name = "data-encoding"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
[[package]]
name = "debug-helper"
version = "0.3.13"
@@ -814,12 +804,11 @@ checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e"
[[package]]
name = "deno_core"
version = "0.175.0"
version = "0.148.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5f1c9d721b3d634a3bbd09cc080d3e995ed411db898757c764b83c4120a36c"
checksum = "8c6af1950eecb34836f6a6ba2a69de4105bb109fe57e45a329ba63b8b6d9a0cc"
dependencies = [
"anyhow",
"bytes",
"deno_ops",
"futures",
"indexmap",
@@ -831,7 +820,6 @@ dependencies = [
"serde",
"serde_json",
"serde_v8",
"smallvec",
"sourcemap",
"url",
"v8",
@@ -839,12 +827,11 @@ dependencies = [
[[package]]
name = "deno_ops"
version = "0.53.0"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d997fae982a12cb9583add9c4ed355686c0dd988aceba9c2bfc8b0db0edc8404"
checksum = "4829d69dd689e487c9b2d36b7698a3f6266f7677ad0c4dead9480efbbadd8f01"
dependencies = [
"once_cell",
"pmutil",
"proc-macro-crate",
"proc-macro2",
"quote",
@@ -990,9 +977,9 @@ dependencies = [
[[package]]
name = "ena"
version = "0.14.2"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
checksum = "b2e5d13ca2353ab7d0230988629def93914a8c4015f621f9b13ed2955614731d"
dependencies = [
"log",
]
@@ -1136,9 +1123,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -1151,9 +1138,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -1161,15 +1148,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -1189,9 +1176,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-lite"
@@ -1210,9 +1197,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@@ -1221,21 +1208,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.27"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -1350,16 +1337,7 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.6",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash 0.8.3",
"ahash",
]
[[package]]
@@ -1368,7 +1346,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
dependencies = [
"hashbrown 0.12.3",
"hashbrown",
]
[[package]]
@@ -1525,9 +1503,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.25"
version = "0.14.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899"
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
dependencies = [
"bytes",
"futures-channel",
@@ -1613,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"hashbrown",
"serde",
]
@@ -1634,11 +1612,10 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.7"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76e86b86ae312accbf05ade23ce76b625e0e47a255712b7414037385a1c05380"
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.45.0",
]
@@ -2175,9 +2152,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.46"
version = "0.10.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
dependencies = [
"bitflags",
"cfg-if",
@@ -2207,9 +2184,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.81"
version = "0.9.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
dependencies = [
"autocfg",
"cc",
@@ -2538,9 +2515,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.52"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
@@ -2548,7 +2525,7 @@ dependencies = [
[[package]]
name = "progenitor"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"anyhow",
"built",
@@ -2566,7 +2543,7 @@ dependencies = [
[[package]]
name = "progenitor-client"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"bytes",
"futures-core",
@@ -2580,7 +2557,7 @@ dependencies = [
[[package]]
name = "progenitor-impl"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"getopts",
"heck",
@@ -2602,7 +2579,7 @@ dependencies = [
[[package]]
name = "progenitor-macro"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"openapiv3",
"proc-macro2",
@@ -2647,9 +2624,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.26"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
@@ -2773,11 +2750,10 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "regress"
version = "0.5.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d995d590bd8ec096d1893f414bf3f5e8b0ee4c9eed9a5642b9766ef2c8e2e8e9"
checksum = "0a92ff21fe8026ce3f2627faaf43606f0b67b014dbc9ccf027181a804f75d92e"
dependencies = [
"hashbrown 0.13.2",
"memchr",
]
@@ -2923,7 +2899,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.17",
"semver 1.0.16",
]
[[package]]
@@ -2977,7 +2953,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#21615cb15898f85f7c8b93253c0faf2ba07c8b2b"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2986,7 +2962,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#21615cb15898f85f7c8b93253c0faf2ba07c8b2b"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
dependencies = [
"bitflags",
"bstr",
@@ -2999,9 +2975,9 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#21615cb15898f85f7c8b93253c0faf2ba07c8b2b"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
dependencies = [
"ahash 0.7.6",
"ahash",
"anyhow",
"itertools",
"lalrpop",
@@ -3147,9 +3123,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.17"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
dependencies = [
"serde",
]
@@ -3171,9 +3147,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.156"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
dependencies = [
"serde_derive",
]
@@ -3200,9 +3176,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.156"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
dependencies = [
"proc-macro2",
"quote",
@@ -3288,9 +3264,9 @@ dependencies = [
[[package]]
name = "serde_v8"
version = "0.86.0"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5237fcbeda49fc0e327fc5b4d419f15851dcb50718958cd0fe31a75345694d79"
checksum = "4cefcd409ebe3d7c4e646cee54fc0f90ea33fb9cc9586d8ffff5ed1d997de1d5"
dependencies = [
"bytes",
"derive_more",
@@ -3440,16 +3416,17 @@ dependencies = [
[[package]]
name = "sourcemap"
version = "6.2.3"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed16231c92d0a6f0388f56e0ab2be24ecff1173f8e22f0ea5e074d0525631cb"
checksum = "6e031f2463ecbdd5f34c950f89f5c1e1032f22c0f8e3dc4bdb2e8b6658cf61eb"
dependencies = [
"data-encoding",
"base64 0.11.0",
"if_chain",
"lazy_static",
"regex",
"rustc_version 0.2.3",
"serde",
"serde_json",
"unicode-id",
"url",
]
@@ -3506,7 +3483,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105"
dependencies = [
"ahash 0.7.6",
"ahash",
"atoi",
"base64 0.13.1",
"bitflags",
@@ -3641,9 +3618,9 @@ dependencies = [
[[package]]
name = "string_enum"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41491e23e7db79343236a6ced96325ff132eb09e29ac4c5b8132b9c55aaaae89"
checksum = "994453cd270ad0265796eb24abf5540091ed03e681c5f3c12bc33e4db33253e1"
dependencies = [
"pmutil",
"proc-macro2",
@@ -3690,11 +3667,11 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.29.37"
version = "0.29.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5005cd73617e18592faa31298225b26f1c407b84a681d67efb735c3d3458e101"
checksum = "2d515be281f603cb97afaa89896aca5e5c748fde11c7f926e35cdaa8ff8da705"
dependencies = [
"ahash 0.7.6",
"ahash",
"ast_node",
"better_scoped_tls",
"cfg-if",
@@ -4075,9 +4052,9 @@ checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
[[package]]
name = "toml_edit"
version = "0.19.7"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
dependencies = [
"indexmap",
"toml_datetime",
@@ -4130,6 +4107,25 @@ 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"
@@ -4278,7 +4274,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typify"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#33832b91d7a75d8b3dfc0673006cbf9661aeeffb"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
dependencies = [
"typify-impl",
"typify-macro",
@@ -4287,13 +4283,14 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#33832b91d7a75d8b3dfc0673006cbf9661aeeffb"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
dependencies = [
"heck",
"log",
"proc-macro2",
"quote",
"regress",
"rustfmt-wrapper",
"schemars",
"serde_json",
"syn",
@@ -4304,7 +4301,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#33832b91d7a75d8b3dfc0673006cbf9661aeeffb"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
dependencies = [
"proc-macro2",
"quote",
@@ -4395,9 +4392,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.12"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b"
checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
[[package]]
name = "unicode-general-category"
@@ -4509,13 +4506,14 @@ dependencies = [
[[package]]
name = "v8"
version = "0.64.0"
version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2287b485fa902172da3722d7e557e083afd63921777e0c6e5c0fba28e6d59d3"
checksum = "5a1cbad73336d67babcbe5e3b03c907c8d2ff77fc6f997570af219bbd9fdb6ce"
dependencies = [
"bitflags",
"fslock",
"lazy_static",
"libc",
"which",
]
@@ -4545,11 +4543,12 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "walkdir"
version = "2.3.3"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
@@ -4702,9 +4701,9 @@ dependencies = [
[[package]]
name = "whoami"
version = "1.4.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68"
checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3"
dependencies = [
"wasm-bindgen",
"web-sys",
@@ -4743,7 +4742,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"axum",
@@ -4770,7 +4769,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"argon2",
@@ -4809,7 +4808,7 @@ dependencies = [
"tokio-util",
"tower",
"tower-cookies",
"tower-http",
"tower-http 0.4.0",
"tracing",
"tracing-subscriber",
"urlencoding",
@@ -4825,7 +4824,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"base64 0.21.0",
"chrono",
@@ -4840,7 +4839,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"chrono",
"serde",
@@ -4853,7 +4852,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"axum",
@@ -4878,7 +4877,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"serde",
"serde_json",
@@ -4886,7 +4885,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"itertools",
@@ -4901,7 +4900,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"itertools",
@@ -4913,7 +4912,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"itertools",
@@ -4928,7 +4927,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"deno_core",
@@ -4942,7 +4941,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"chrono",
@@ -4965,7 +4964,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.79.0"
version = "1.74.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -5024,9 +5023,9 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@@ -5039,51 +5038,51 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.6"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
dependencies = [
"memchr",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.79.0"
version = "1.74.2"
authors.workspace = true
edition.workspace = true
@@ -19,7 +19,7 @@ members = [
]
[workspace.package]
version = "1.79.0"
version = "1.74.2"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -2501,57 +2501,6 @@
},
"query": "SELECT set_config('session.user', $1, true)"
},
"6d50a8dc9cfc040b6f37b58053daa0709671e008a10ab4189114ceff66efe603": {
"describe": {
"columns": [
{
"name": "worker",
"ordinal": 0,
"type_info": "Varchar"
},
{
"name": "worker_instance",
"ordinal": 1,
"type_info": "Varchar"
},
{
"name": "last_ping",
"ordinal": 2,
"type_info": "Int4"
},
{
"name": "started_at",
"ordinal": 3,
"type_info": "Timestamptz"
},
{
"name": "ip",
"ordinal": 4,
"type_info": "Varchar"
},
{
"name": "jobs_executed",
"ordinal": 5,
"type_info": "Int4"
}
],
"nullable": [
false,
false,
null,
false,
false,
false
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
}
},
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2"
},
"6dfd1ac7168f098a9e7c92d530a470c0a156041d5081053d43d9efb00d81c464": {
"describe": {
"columns": [
@@ -2573,6 +2522,57 @@
},
"query": "SELECT policy from app WHERE path = $1 AND workspace_id = $2"
},
"6fc2cfae9df83eb24ea33e4c9567740100f4dd2285afc3ef474fc70041b0567b": {
"describe": {
"columns": [
{
"name": "worker",
"ordinal": 0,
"type_info": "Varchar"
},
{
"name": "worker_instance",
"ordinal": 1,
"type_info": "Varchar"
},
{
"name": "ping_at",
"ordinal": 2,
"type_info": "Timestamptz"
},
{
"name": "started_at",
"ordinal": 3,
"type_info": "Timestamptz"
},
{
"name": "ip",
"ordinal": 4,
"type_info": "Varchar"
},
{
"name": "jobs_executed",
"ordinal": 5,
"type_info": "Int4"
}
],
"nullable": [
false,
false,
false,
false,
false,
false
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
}
},
"query": "SELECT * FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2"
},
"701f215eb14ba67a79afea15d7effc0dd394ba6c4a72c95d4560c5a377015d4e": {
"describe": {
"columns": [],
@@ -2984,6 +2984,20 @@
},
"query": "UPDATE group_ SET summary = $1 WHERE name = $2 AND workspace_id = $3"
},
"83fc9bf32b9b40987feff61fea82e20613e74efb7bf8ba52485566667ccf2284": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
}
},
"query": "UPDATE resource SET path = $1 WHERE path = $2 AND workspace_id = $3"
},
"8543f029d9784234e4c6a6dcd7b03e62d544b98be261334ee210594e0bb839f2": {
"describe": {
"columns": [
@@ -3154,21 +3168,6 @@
},
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, false, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage"
},
"8c0131a9cc61f2daa258d49767242bcaab6bb34a977ff7fb0c18aa9202d11f47": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Jsonb",
"Text",
"Text"
]
}
},
"query": "UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4"
},
"8c11511a74a41a65f448249a00ebe6964a61d00c2f7b4875a55e64741bf1f0ca": {
"describe": {
"columns": [
@@ -4698,27 +4697,6 @@
},
"query": "SELECT null FROM queue WHERE id = $1 FOR UPDATE"
},
"be1de9116b18a40681cb8c5ec7578fd7c5f7ce77f7af63f766c2899d85fe0bef": {
"describe": {
"columns": [
{
"name": "value",
"ordinal": 0,
"type_info": "Jsonb"
}
],
"nullable": [
true
],
"parameters": {
"Left": [
"Text",
"Text"
]
}
},
"query": "SELECT value FROM resource WHERE path = $1 AND workspace_id = $2"
},
"be7a99a5bb6858323ca61dd51077010f51ba58ae76b9a413339255024dcb524d": {
"describe": {
"columns": [],

View File

@@ -112,7 +112,6 @@ Windmill Community Edition {GIT_VERSION}
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"NPM_CONFIG_REGISTRY",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.79.0
version: 1.74.2
title: Windmill API
contact:
@@ -204,6 +204,7 @@ paths:
schema:
type: string
/w/{workspace}/users/is_owner/{path}:
get:
summary: is owner of path
@@ -1548,7 +1549,7 @@ paths:
required: true
content:
application/json:
schema:
schema:
type: object
properties:
value: {}
@@ -2102,6 +2103,7 @@ paths:
items:
type: string
/w/{workspace}/scripts/create:
post:
summary: create script
@@ -2833,6 +2835,7 @@ paths:
schema:
$ref: "#/components/schemas/AppWithLastVersion"
/w/{workspace}/apps/secret_of/{path}:
get:
summary: get public secret of app
@@ -3165,14 +3168,12 @@ paths:
- $ref: "#/components/parameters/ScriptExactPath"
- $ref: "#/components/parameters/ScriptStartPath"
- $ref: "#/components/parameters/ScriptExactHash"
- $ref: "#/components/parameters/StartedBefore"
- $ref: "#/components/parameters/StartedAfter"
- $ref: "#/components/parameters/CreatedBefore"
- $ref: "#/components/parameters/CreatedAfter"
- $ref: "#/components/parameters/Success"
- $ref: "#/components/parameters/JobKinds"
- $ref: "#/components/parameters/Suspended"
- $ref: "#/components/parameters/Running"
- $ref: "#/components/parameters/ArgsFilter"
- $ref: "#/components/parameters/ResultFilter"
responses:
"200":
description: All available queued jobs
@@ -3197,12 +3198,10 @@ paths:
- $ref: "#/components/parameters/ScriptExactPath"
- $ref: "#/components/parameters/ScriptStartPath"
- $ref: "#/components/parameters/ScriptExactHash"
- $ref: "#/components/parameters/StartedBefore"
- $ref: "#/components/parameters/StartedAfter"
- $ref: "#/components/parameters/CreatedBefore"
- $ref: "#/components/parameters/CreatedAfter"
- $ref: "#/components/parameters/Success"
- $ref: "#/components/parameters/JobKinds"
- $ref: "#/components/parameters/ArgsFilter"
- $ref: "#/components/parameters/ResultFilter"
- name: is_skipped
description: is the job skipped
in: query
@@ -3236,11 +3235,9 @@ paths:
- $ref: "#/components/parameters/ScriptExactPath"
- $ref: "#/components/parameters/ScriptStartPath"
- $ref: "#/components/parameters/ScriptExactHash"
- $ref: "#/components/parameters/StartedBefore"
- $ref: "#/components/parameters/StartedAfter"
- $ref: "#/components/parameters/CreatedBefore"
- $ref: "#/components/parameters/CreatedAfter"
- $ref: "#/components/parameters/JobKinds"
- $ref: "#/components/parameters/ArgsFilter"
- $ref: "#/components/parameters/ResultFilter"
- name: is_skipped
description: is the job skipped
in: query
@@ -3441,7 +3438,7 @@ paths:
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
get:
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
operationId: getResumeUrls
tags:
- job
@@ -3467,7 +3464,7 @@ paths:
properties:
approvalPage:
type: string
resume:
resume:
type: string
cancel:
type: string
@@ -4112,7 +4109,7 @@ paths:
type: string
owners:
type: array
items:
items:
type: string
extra_perms:
additionalProperties:
@@ -4146,7 +4143,7 @@ paths:
properties:
owners:
type: array
items:
items:
type: string
extra_perms:
additionalProperties:
@@ -4317,8 +4314,7 @@ paths:
required: true
schema:
type: string
enum:
[script, group_, resource, schedule, variable, flow, folder, app]
enum: [script, group_, resource, schedule, variable, flow, folder, app]
responses:
"200":
description: acls
@@ -4343,8 +4339,7 @@ paths:
required: true
schema:
type: string
enum:
[script, group_, resource, schedule, variable, flow, folder, app]
enum: [script, group_, resource, schedule, variable, flow, folder, app]
requestBody:
description: acl to add
required: true
@@ -4380,8 +4375,7 @@ paths:
required: true
schema:
type: string
enum:
[script, group_, resource, schedule, variable, flow, folder, app]
enum: [script, group_, resource, schedule, variable, flow, folder, app]
requestBody:
description: acl to add
required: true
@@ -4401,18 +4395,19 @@ paths:
schema:
type: string
/w/{workspace}/capture_u/{path}:
post:
summary: update flow preview capture
operationId: updateCapture
tags:
- capture
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"204":
description: flow preview captured
post:
summary: update flow preview capture
operationId: updateCapture
tags:
- capture
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"204":
description: flow preview captured
/w/{workspace}/capture/{path}:
put:
@@ -4619,15 +4614,15 @@ components:
in: query
schema:
type: string
StartedBefore:
name: started_before
CreatedBefore:
name: created_before
description: filter on created before (inclusive) timestamp
in: query
schema:
type: string
format: date-time
StartedAfter:
name: started_after
CreatedAfter:
name: created_after
description: filter on created after (exclusive) timestamp
in: query
schema:
@@ -4651,19 +4646,6 @@ components:
in: query
schema:
type: boolean
ArgsFilter:
name: args
description: filter on jobs containing those args as a json subset (@> in postgres)
in: query
schema:
type: string
ResultFilter:
name: result
description: filter on jobs containing those result as a json subset (@> in postgres)
in: query
schema:
type: string
After:
name: after
description: filter on created after (exclusive) timestamp
@@ -5568,8 +5550,9 @@ components:
type: string
worker_instance:
type: string
last_ping:
type: number
ping_at:
type: string
format: date-time
started_at:
type: string
format: date-time

View File

@@ -16,9 +16,10 @@ use axum::{
routing::{delete, get, post},
Json, Router,
};
use itertools::Itertools;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{self, to_anyhow, Error, JsonResult, Result},
error::{self, Error, JsonResult, Result},
users::username_to_permissioned_as,
utils::{not_found_if_none, paginate, Pagination},
};
@@ -261,26 +262,13 @@ async fn update_folder(
sqlb.and_where_eq("workspace_id", "?".bind(&w_id));
if let Some(display_name) = ng.display_name {
sqlb.set("display_name", "?".bind(&display_name));
sqlb.set("display_name", display_name);
}
if let Some(owners) = ng.owners {
sqlb.set(
"owners",
"?".bind(&format!(
"{{{}}}",
owners
.iter()
.map(|x| format!("\"{x}\""))
.collect::<Vec<_>>()
.join(","),
)),
);
sqlb.set_str("owners", format!("{{{}}}", owners.into_iter().join(",")));
}
if let Some(extra_perms) = ng.extra_perms {
sqlb.set(
"extra_perms",
"?".bind(&serde_json::to_string(&extra_perms).map_err(to_anyhow)?),
);
sqlb.set_str("extra_perms", extra_perms.to_string());
}
sqlb.returning("*");

View File

@@ -300,6 +300,9 @@ pub fn add_include_headers(
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<_>>())
@@ -318,22 +321,19 @@ pub fn add_include_headers(
});
args
}
#[derive(Deserialize)]
pub struct ListQueueQuery {
pub script_path_start: Option<String>,
pub script_path_exact: Option<String>,
pub script_hash: Option<String>,
pub created_by: Option<String>,
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
pub created_after: Option<chrono::DateTime<chrono::Utc>>,
pub running: Option<bool>,
pub parent_job: Option<String>,
pub order_desc: Option<bool>,
pub job_kinds: Option<String>,
pub suspended: Option<bool>,
// filter by matching a subset of the args using base64 encoded json subset
pub args: Option<String>,
}
fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> SqlBuilder {
@@ -362,11 +362,11 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
if let Some(pj) = &lq.parent_job {
sqlb.and_where_eq("parent_job", "?".bind(pj));
}
if let Some(dt) = &lq.started_before {
sqlb.and_where_le("started_at", format!("to_timestamp({})", dt.timestamp()));
if let Some(dt) = &lq.created_before {
sqlb.and_where_lt("created_at", format!("to_timestamp({})", dt.timestamp()));
}
if let Some(dt) = &lq.started_after {
sqlb.and_where_ge("started_at", format!("to_timestamp({})", dt.timestamp()));
if let Some(dt) = &lq.created_after {
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
}
if let Some(s) = &lq.suspended {
@@ -376,7 +376,6 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
sqlb.and_where_eq("suspend", 0);
}
}
if let Some(jk) = &lq.job_kinds {
sqlb.and_where_in(
"job_kind",
@@ -384,10 +383,6 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
);
}
if let Some(args) = &lq.args {
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
}
sqlb
}
@@ -460,14 +455,13 @@ async fn list_jobs(
script_path_exact: lq.script_path_exact,
script_hash: lq.script_hash,
created_by: lq.created_by,
started_before: lq.started_before,
started_after: lq.started_after,
created_before: lq.created_before,
created_after: lq.created_after,
running: None,
parent_job: lq.parent_job,
order_desc: Some(true),
job_kinds: lq.job_kinds,
suspended: lq.suspended,
args: lq.args,
},
&[
"'QueuedJob' as typ",
@@ -1142,14 +1136,14 @@ where
struct InPayload {
payload: Option<String>,
}
}
}
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
let vec = base64::engine::general_purpose::URL_SAFE
.decode(t)
.context("invalid base64")?;
serde_json::from_slice(vec.as_slice()).context("invalid json")
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
let vec = base64::engine::general_purpose::URL_SAFE
.decode(t)
.context("invalid base64")?;
serde_json::from_slice(vec.as_slice()).context("invalid json")
}
}
}
pub async fn run_flow_by_path(
authed: Authed,
@@ -1693,11 +1687,11 @@ fn list_completed_jobs_query(
if let Some(pj) = &lq.parent_job {
sqlb.and_where_eq("parent_job", "?".bind(pj));
}
if let Some(dt) = &lq.started_before {
sqlb.and_where_le("started_at", format!("to_timestamp({})", dt.timestamp()));
if let Some(dt) = &lq.created_before {
sqlb.and_where_lt("created_at", format!("to_timestamp({})", dt.timestamp()));
}
if let Some(dt) = &lq.started_after {
sqlb.and_where_ge("started_at", format!("to_timestamp({})", dt.timestamp()));
if let Some(dt) = &lq.created_after {
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
}
if let Some(sk) = &lq.is_skipped {
sqlb.and_where_eq("is_skipped", sk);
@@ -1712,14 +1706,6 @@ fn list_completed_jobs_query(
);
}
if let Some(args) = &lq.args {
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
}
if let Some(result) = &lq.result {
sqlb.and_where("result @> ?".bind(&result.replace("'", "''")));
}
sqlb
}
#[derive(Deserialize, Clone)]
@@ -1728,8 +1714,8 @@ pub struct ListCompletedQuery {
pub script_path_exact: Option<String>,
pub script_hash: Option<String>,
pub created_by: Option<String>,
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
pub created_after: Option<chrono::DateTime<chrono::Utc>>,
pub success: Option<bool>,
pub parent_job: Option<String>,
pub order_desc: Option<bool>,
@@ -1737,10 +1723,6 @@ pub struct ListCompletedQuery {
pub is_skipped: Option<bool>,
pub is_flow_step: Option<bool>,
pub suspended: Option<bool>,
// filter by matching a subset of the args using base64 encoded json subset
pub args: Option<String>,
// filter by matching a subset of the result using base64 encoded json subset
pub result: Option<String>,
}
async fn list_completed_jobs(

View File

@@ -26,7 +26,6 @@ use axum::{
Json, Router,
};
use hyper::StatusCode;
use serde_json::Value;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{Error, JsonResult, Result},
@@ -379,27 +378,9 @@ async fn update_variable(
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db).await?;
}
let mut v = sqlx::query_scalar!(
"SELECT value FROM resource WHERE path = $1 AND workspace_id = $2",
path,
w_id
)
.fetch_optional(&mut tx)
.await?
.flatten();
if let Some(old_v) = v {
v = Some(replace_path(
old_v,
&format!("$var:{path}"),
&format!("$var:{npath}"),
))
}
sqlx::query!(
"UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4",
"UPDATE resource SET path = $1 WHERE path = $2 AND workspace_id = $3",
npath,
v,
path,
w_id
)
@@ -438,23 +419,6 @@ async fn update_variable(
Ok(format!("variable {} updated (npath: {:?})", path, npath))
}
fn replace_path(v: serde_json::Value, path: &str, npath: &str) -> Value {
match v {
Value::Object(v) => Value::Object(
v.into_iter()
.map(|(k, v)| (k, replace_path(v, path, npath)))
.collect(),
),
Value::Array(arr) => Value::Array(
arr.into_iter()
.map(|v| replace_path(v, path, npath))
.collect(),
),
Value::String(s) if s == path => Value::String(npath.to_owned()),
_ => v,
}
}
pub async fn build_crypt<'c>(
db: &mut Transaction<'c, Postgres>,
w_id: &str,

View File

@@ -28,7 +28,7 @@ pub fn global_service() -> Router {
struct WorkerPing {
worker: String,
worker_instance: String,
last_ping: Option<i32>,
ping_at: chrono::DateTime<chrono::Utc>,
started_at: chrono::DateTime<chrono::Utc>,
ip: String,
jobs_executed: i32,
@@ -45,7 +45,7 @@ async fn list_worker_pings(
let rows = sqlx::query_as!(
WorkerPing,
"SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2",
"SELECT * FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2",
per_page as i64,
offset as i64
)

View File

@@ -1073,7 +1073,6 @@ struct ScriptMetadata {
schema: Option<Schema>,
is_template: bool,
lock: Vec<String>,
kind: String,
}
enum ArchiveImpl {
@@ -1236,7 +1235,6 @@ async fn tarball_workspace(
description: script.description,
schema: script.schema,
is_template: script.is_template,
kind: script.kind.to_string(),
lock,
};
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();

View File

@@ -7,7 +7,7 @@
*/
use std::{
fmt::{self, Display},
fmt::Display,
hash::{Hash, Hasher},
};
@@ -103,18 +103,6 @@ pub enum ScriptKind {
Approval,
}
impl Display for ScriptKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(match self {
ScriptKind::Trigger => "trigger",
ScriptKind::Failure => "failure",
ScriptKind::Script => "script",
ScriptKind::Approval => "approval",
})?;
Ok(())
}
}
#[derive(Serialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Script {

View File

@@ -236,7 +236,8 @@ pub async fn create_token_for_owner<'c>(
) -> error::Result<(Transaction<'c, Postgres>, String)> {
// TODO: Bad implementation. We should not have access to this DB here.
let token: String = rd_string(30);
let is_super_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
let is_super_admin = owner.contains('@')
&& sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", owner)
.fetch_optional(&mut tx)
.await?
.unwrap_or(false);
@@ -263,7 +264,7 @@ const ROOT_CACHE_DIR: &str = "/tmp/windmill/cache/";
const PIP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "pip");
const DENO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "deno");
const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go");
const NUM_SECS_PING: u64 = 5;
const NUM_SECS_ENV_CHECK: u64 = 15;
const NUM_SECS_SYNC: u64 = 60 * 10;
const INCLUDE_DEPS_PY_SH_CONTENT: &str = include_str!("../nsjail/download_deps.py.sh");
@@ -329,7 +330,7 @@ lazy_static::lazy_static! {
.ok()
.map(|x| format!(";{x}"))
.unwrap_or_else(|| String::new());
static ref NPM_CONFIG_REGISTRY: Option<String> = std::env::var("NPM_CONFIG_REGISTRY").ok();
@@ -552,7 +553,7 @@ pub async fn run_worker(
let do_break = async {
if last_ping.elapsed().as_secs() > NUM_SECS_PING {
if last_ping.elapsed().as_secs() > NUM_SECS_ENV_CHECK {
sqlx::query!(
"UPDATE worker_ping SET ping_at = now(), jobs_executed = $1 WHERE worker = $2",
jobs_executed,
@@ -1480,26 +1481,6 @@ fn capitalize(s: &str) -> String {
}
}
fn get_common_deno_proc_envs(token: &str, base_internal_url: &str) -> HashMap<String, String> {
let hostname_base = BASE_URL.split("://").last().unwrap_or("localhost");
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
let deno_auth_tokens_base = DENO_AUTH_TOKENS.as_str();
let deno_auth_tokens =
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens_base}",);
let mut deno_envs: HashMap<String, String> = HashMap::from([
(String::from("PATH"), PATH_ENV.clone()),
(String::from("DENO_AUTH_TOKENS"), deno_auth_tokens),
(String::from("BASE_INTERNAL_URL"), base_internal_url.to_string()),
(String::from("NO_COLOR"), String::from("true")),
]);
if let Some(ref s) = *NPM_CONFIG_REGISTRY {
deno_envs.insert(String::from("NPM_CONFIG_REGISTRY"), s.clone());
}
return deno_envs;
}
#[tracing::instrument(level = "trace", skip_all)]
async fn handle_deno_job(
logs: &mut String,
@@ -1565,9 +1546,11 @@ run().catch(async (e) => {{
let mut reserved_variables = get_reserved_variables(job, &token, db).await?;
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
let common_deno_proc_envs = get_common_deno_proc_envs(&token, base_internal_url);
//do not cache local dependencies
let reload = format!("--reload={base_internal_url}");
let hostname_base = BASE_URL.split("://").last().unwrap_or("localhost");
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
let deno_auth_tokens_base = DENO_AUTH_TOKENS.as_str();
let deno_auth_tokens =
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens_base}",);
let child = async {
Ok(if !*DISABLE_NSJAIL {
let _ = write_file(
@@ -1591,7 +1574,6 @@ run().catch(async (e) => {{
}
args.push("--import-map");
args.push("/tmp/import_map.json");
args.push(&reload);
args.push("--unstable");
if let Some(deno_flags) = DENO_FLAGS.as_ref() {
for flag in deno_flags {
@@ -1606,7 +1588,9 @@ run().catch(async (e) => {{
.current_dir(job_dir)
.env_clear()
.envs(reserved_variables)
.envs(common_deno_proc_envs)
.env("PATH", PATH_ENV.as_str())
.env("DENO_AUTH_TOKENS", deno_auth_tokens)
.env("BASE_INTERNAL_URL", base_internal_url)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
@@ -1618,7 +1602,6 @@ run().catch(async (e) => {{
args.push("run");
args.push("--import-map");
args.push(&import_map_path);
args.push(&reload);
args.push("--unstable");
if let Some(deno_flags) = DENO_FLAGS.as_ref() {
for flag in deno_flags {
@@ -1632,8 +1615,11 @@ run().catch(async (e) => {{
.current_dir(job_dir)
.env_clear()
.envs(reserved_variables)
.envs(common_deno_proc_envs)
.env("PATH", PATH_ENV.as_str())
.env("DENO_DIR", DENO_CACHE_DIR)
.env("DENO_AUTH_TOKENS", deno_auth_tokens)
.env("BASE_INTERNAL_URL", base_internal_url)
.env("NO_COLOR", "true")
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())

View File

@@ -28,18 +28,14 @@ export class AppFile implements Resource, PushDiffs {
remotePath: string,
diffs: Difference[],
): Promise<void> {
let app: AppWithLastVersion | undefined = undefined;
try {
app = await AppService.getAppByPath({ workspace, path: remotePath });
} catch (e) {}
if (app) {
if (await AppService.existsApp({ workspace, path: remotePath })) {
console.log(
colors.bold.yellow(
`Applying ${diffs.length} diffs to existing app... ${remotePath}`,
`Applying ${diffs.length} diffs to existing app...`,
),
);
const changeset: {
path?: string | undefined;
summary?: string | undefined;
value?: any;
policy?: Policy | undefined;
@@ -50,7 +46,7 @@ export class AppFile implements Resource, PushDiffs {
(
diff.path[0] !== "value" && diff.path[0] !== "policy" && (
diff.path.length !== 1 ||
!["summary"].includes(
!["path", "summary"].includes(
diff.path[0] as string,
)
)
@@ -65,13 +61,6 @@ export class AppFile implements Resource, PushDiffs {
}
}
if ((!changeset?.policy || JSON.stringify(changeset?.policy) == JSON.stringify(app.policy))
&& (!changeset?.value || JSON.stringify(changeset?.value) == JSON.stringify(app.value))
&& (!changeset?.summary || changeset.summary == app.summary)) {
console.log(colors.yellow(`No changes to push for app ${remotePath}, skipping`))
return;
}
const hasChanges = Object.values(changeset).some((v) =>
v !== null && typeof v !== "undefined"
);
@@ -98,10 +87,19 @@ export class AppFile implements Resource, PushDiffs {
}
}
async push(workspace: string, remotePath: string): Promise<void> {
let existing: AppWithLastVersion | undefined;
try {
existing = await AppService.getAppByPath({
workspace: workspace,
path: remotePath,
});
} catch {
existing = undefined;
}
await this.pushDiffs(
workspace,
remotePath,
microdiff({}, this, { cyclesFix: false }),
microdiff(existing ?? {}, this, { cyclesFix: false }),
);
}
}

View File

@@ -18,7 +18,7 @@ export type Context = {
};
async function tryResolveWorkspace(
opts: GlobalOptions
opts: GlobalOptions,
): Promise<
{ isError: false; value: Workspace } | { isError: true; error: string }
> {
@@ -49,20 +49,17 @@ async function tryResolveWorkspace(
}
export async function resolveWorkspace(
opts: GlobalOptions
opts: GlobalOptions,
): Promise<Workspace> {
const res = await tryResolveWorkspace(opts);
if (res.isError) {
console.log(colors.red.bold(res.error));
return Deno.exit(-1);
} else {
return res.value;
}
}
export async function requireLogin(
opts: GlobalOptions
): Promise<GlobalUserInfo> {
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
const workspace = await resolveWorkspace(opts);
let token = await tryGetLoginInfo(opts);
@@ -76,7 +73,7 @@ export async function requireLogin(
return await UserService.globalWhoami();
} catch {
console.log(
"! Could not reach API given existing credentials. Attempting to reauth..."
"! Could not reach API given existing credentials. Attempting to reauth...",
);
const newToken = await loginInteractive(workspace.remote);
if (!newToken) {
@@ -88,14 +85,14 @@ export async function requireLogin(
setClient(
token,
workspace.remote.substring(0, workspace.remote.length - 1)
workspace.remote.substring(0, workspace.remote.length - 1),
);
return await UserService.globalWhoami();
}
}
export async function tryResolveVersion(
opts: GlobalOptions
opts: GlobalOptions,
): Promise<number | undefined> {
if ((opts as any).__cache_version) {
return (opts as any).__cache_version;
@@ -105,12 +102,12 @@ export async function tryResolveVersion(
if (workspaceRes.isError) return undefined;
const response = await fetch(
new URL(new URL(workspaceRes.value.remote).origin + "/api/version")
new URL(new URL(workspaceRes.value.remote).origin + "/api/version"),
);
const version = await response.text();
try {
return Number.parseInt(
version.split("-", 1)[0].replaceAll(".", "").replace("v", "")
version.split("-", 1)[0].replaceAll(".", "").replace("v", ""),
);
} catch {
return undefined;
@@ -119,7 +116,7 @@ export async function tryResolveVersion(
export async function validatePath(
opts: GlobalOptions,
path: string
path: string,
): Promise<boolean> {
const backendVersion = await tryResolveVersion(opts);
if (path.startsWith("f")) {
@@ -127,16 +124,19 @@ export async function validatePath(
return true;
}
console.log(
`Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.`
`Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.`,
);
return false;
}
if (!(path.startsWith("g") || path.startsWith("u"))) {
if (
!(path.startsWith("g") ||
path.startsWith("u"))
) {
console.log(
colors.red(
"Given remote path looks invalid. Remote paths are typically of the form <u|g|f>/<username|group|folder>/..."
)
"Given remote path looks invalid. Remote paths are typically of the form <u|g|f>/<username|group|folder>/...",
),
);
return false;
}

View File

@@ -94,7 +94,6 @@ export class FlowFile implements Resource, PushDiffs {
...changeset,
...base_changeset,
}
await FlowService.updateFlow({
workspace: workspace,
path: remotePath,
@@ -115,11 +114,20 @@ export class FlowFile implements Resource, PushDiffs {
}
}
async push(workspace: string, remotePath: string): Promise<void> {
let remote: Flow | undefined;
try {
remote = await FlowService.getFlowByPath({
workspace,
path: remotePath,
});
} catch {
remote = undefined;
}
await this.pushDiffs(
workspace,
remotePath,
microdiff({}, this, { cyclesFix: false }),
microdiff(remote ?? {}, this, { cyclesFix: false }),
);
}
}

View File

@@ -1,4 +1,4 @@
import { colors, Command, FolderService, microdiff } from "./deps.ts";
import { colors, Command, Folder, FolderService, microdiff } from "./deps.ts";
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
import {
Difference,
@@ -20,16 +20,8 @@ import {
export class FolderFile implements Resource, PushDiffs {
@property(array(() => String))
owners: Array<string> | undefined;
@property(
map(
() => String,
() => Boolean,
{ shape: MapShape.Object }
)
)
@property(map(() => String, () => Boolean, { shape: MapShape.Object }))
extra_perms: Map<string, boolean> | undefined;
@property(() => String)
display_name: string | undefined;
async push(workspace: string, remotePath: string): Promise<void> {
if (remotePath.startsWith("/")) {
@@ -39,17 +31,23 @@ export class FolderFile implements Resource, PushDiffs {
remotePath = remotePath.substring(2);
}
let existing: Folder | undefined;
try {
existing = await FolderService.getFolder({ workspace, name: remotePath });
} catch {
existing = undefined;
}
await this.pushDiffs(
workspace,
remotePath,
microdiff({}, this, { cyclesFix: false })
microdiff(existing ?? {}, this, { cyclesFix: false }),
);
}
async pushDiffs(
workspace: string,
remotePath: string,
diffs: Difference[]
diffs: Difference[],
): Promise<void> {
if (remotePath.startsWith("/")) {
remotePath = remotePath.substring(1);
@@ -61,34 +59,29 @@ export class FolderFile implements Resource, PushDiffs {
// TODO: Support this in backend
let exists: boolean;
try {
exists = !!(await FolderService.getFolder({
workspace,
name: remotePath,
}));
exists = !!await FolderService.getFolder({ workspace, name: remotePath });
} catch {
exists = false;
}
if (exists) {
console.log(
colors.bold.yellow(
`Applying ${diffs.length} diffs to existing folder... ${remotePath}`
)
`Applying ${diffs.length} diffs to existing folder...`,
),
);
const changeset: {
owners?: string[] | undefined;
extra_perms?: any;
display_name?: string | undefined;
} = {};
for (const diff of diffs) {
if (
diff.type !== "REMOVE" &&
(diff.path.length !== 1 ||
!["owners", "extra_perms", "display_name"].includes(
diff.path[0] as string
))
(
diff.path.length !== 1 ||
!["owners", "extra_perms"].includes(diff.path[0] as string)
)
) {
console.log(diff.path);
throw new Error("Invalid folder diff with path " + diff.path);
}
if (diff.type === "CREATE" || diff.type === "CHANGE") {
@@ -98,27 +91,18 @@ export class FolderFile implements Resource, PushDiffs {
}
}
const hasChanges = Object.values(changeset).some(
(v) => v !== null && typeof v !== "undefined"
const hasChanges = Object.values(changeset).some((v) =>
v !== null && typeof v !== "undefined"
);
if (!hasChanges) {
return;
}
try {
await FolderService.updateFolder({
workspace: workspace,
name: remotePath,
requestBody: {
...changeset,
extra_perms: changeset.extra_perms
? Object.fromEntries(this.extra_perms?.entries() ?? [])
: undefined,
},
});
} catch (e) {
console.error(colors.red.bold(e.body));
throw e;
}
await FolderService.updateFolder({
workspace: workspace,
name: remotePath,
requestBody: changeset,
});
} else {
console.log(colors.bold.yellow("Creating new folder: " + remotePath));
await FolderService.createFolder({
@@ -137,7 +121,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
if (!(await validatePath(opts, remotePath))) {
if (!await validatePath(opts, remotePath)) {
return;
}
@@ -155,11 +139,11 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
export async function pushFolder(
workspace: string,
filePath: string,
remotePath: string
remotePath: string,
) {
const data = decoverto
.type(FolderFile)
.rawToInstance(await Deno.readTextFile(filePath));
const data = decoverto.type(FolderFile).rawToInstance(
await Deno.readTextFile(filePath),
);
data.push(workspace, remotePath);
}
@@ -167,7 +151,7 @@ const command = new Command()
.description("resource related commands")
.command(
"push",
"push a local folder spec. This overrides any remote versions."
"push a local folder spec. This overrides any remote versions.",
)
.arguments("<file_path:string> <remote_path:string>")
.action(push as any);

View File

@@ -1,9 +1,4 @@
import {
Command,
CompletionsCommand,
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";
@@ -18,18 +13,19 @@ import sync from "./sync.ts";
import { tryResolveVersion } from "./context.ts";
import { GlobalOptions } from "./types.ts";
export const VERSION = "v1.77.0";
const VERSION = "v1.74.2";
let command: any = new Command()
.name("wmill")
.description("A simple CLI tool for windmill.")
.action(() => command.showHelp())
.globalOption(
"--workspace <workspace:string>",
"Specify the target workspace. This overrides the default workspace."
"Specify the target workspace. This overrides the default workspace.",
)
.globalOption(
"--token <token:string>",
"Specify an API token. This will override any stored token."
"Specify an API token. This will override any stored token.",
)
.version(VERSION)
.command("flow", flow)
@@ -63,12 +59,14 @@ let command: any = new Command()
"--unstable",
],
provider: new DenoLandProvider({ name: "wmill" }),
})
}),
)
.command("completions", new CompletionsCommand());
if (Number.parseInt(VERSION.replace("v", "").replace(".", "")) > 1700) {
command = command.command("push", push).command("pull", pull);
command = command
.command("push", push)
.command("pull", pull);
}
try {

View File

@@ -13,6 +13,7 @@ import {
EditResourceType,
microdiff,
ResourceService,
ResourceType,
Table,
} from "./deps.ts";
import { Any, decoverto, model, property } from "./decoverto.ts";
@@ -25,10 +26,19 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
description?: string;
async push(workspace: string, remotePath: string): Promise<void> {
await this.pushDiffs(
let existing: ResourceType | undefined;
try {
existing = await ResourceService.getResourceType({
workspace,
path: remotePath,
});
} catch {
existing = undefined;
}
this.pushDiffs(
workspace,
remotePath,
microdiff({}, this, { cyclesFix: false }),
microdiff(existing ?? {}, this, { cyclesFix: false }),
);
}
@@ -55,8 +65,8 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
return;
}
console.log(
colors.yellow.bold(
`Applying ${diffs.length} diffs to existing resource type... ${remotePath}`,
colors.yellow(
`Applying ${diffs.length} diffs to existing resource type...`,
),
);
const changeset: EditResourceType = {};

View File

@@ -43,7 +43,7 @@ export class ResourceFile implements Resource2, PushDiffs {
})
) {
console.log(
colors.yellow.bold(`Applying ${diffs.length} diffs to existing resource... ${remotePath}`),
colors.yellow(`Applying ${diffs.length} diffs to existing resource...`),
);
const changeset: EditResource = {
@@ -51,7 +51,11 @@ export class ResourceFile implements Resource2, PushDiffs {
};
for (const diff of diffs) {
if (diff.path[0] === "is_oauth") {
//is_oauth is not updatable
console.log(
colors.yellow(
"! is_oauth has been removed in newer versions. Ignoring.",
),
);
continue;
}
if (
@@ -60,10 +64,9 @@ export class ResourceFile implements Resource2, PushDiffs {
diff.path[0] !== "value" && (
diff.path.length !== 1 ||
diff.path[0] !== "description"
) && diff.path[0] !== "resource_type"
)
)
) {
console.log(colors.red("Invalid variable diff with path " + diff.path));
throw new Error("Invalid folder diff with path " + diff.path);
}
if (diff.type === "CREATE" || diff.type === "CHANGE") {
@@ -107,10 +110,19 @@ export class ResourceFile implements Resource2, PushDiffs {
}
}
async push(workspace: string, remotePath: string): Promise<void> {
let existing: Resource | undefined;
try {
existing = await ResourceService.getResource({
workspace,
path: remotePath,
});
} catch {
existing = undefined;
}
await this.pushDiffs(
workspace,
remotePath,
microdiff({}, this, { cyclesFix: false }),
microdiff(existing ?? {}, this, { cyclesFix: false }),
);
}
}

View File

@@ -32,11 +32,8 @@ export class ScriptFile {
if (data == null) return data;
if (
data === "script" ||
data === "failure" ||
data === "trigger" ||
data === "command" ||
data === "approvial"
data === "script" || data === "failure" || data === "trigger" ||
data === "command" || data === "approvial"
) {
return data;
}
@@ -45,7 +42,6 @@ export class ScriptFile {
},
toPlain: (data) => data,
})
@property(() => String)
kind?: "script" | "failure" | "trigger" | "command" | "approval";
constructor(summary: string, description: string) {
@@ -59,10 +55,10 @@ async function push(
opts: PushOptions,
filePath: string,
remotePath: string,
contentPath?: string
contentPath?: string,
) {
const workspace = await resolveWorkspace(opts);
if (!(await validatePath(opts, remotePath))) {
if (!await validatePath(opts, remotePath)) {
return;
}
@@ -84,98 +80,53 @@ async function push(
console.log(colors.bold.underline.green(`Script ${remotePath} pushed`));
}
export async function handleScriptMetadata(
path: string,
workspace: string,
alreadySynced: string[]
): Promise<boolean> {
export async function handleScriptMetadata(path: string, workspace: string, alreadySynced: string[]): Promise<boolean> {
if (path.endsWith(".script.json")) {
const contentPath = await findContentFile(path);
return handleFile(
contentPath,
await Deno.readTextFile(contentPath),
workspace,
alreadySynced
);
const contentPath = await findContentFile(path)
return handleFile(contentPath, await Deno.readTextFile(contentPath), workspace, alreadySynced)
} else {
return false;
return false
}
}
export async function handleFile(
path: string,
content: string,
workspace: string,
alreadySynced: string[]
): Promise<boolean> {
if (
path.endsWith(".ts") ||
path.endsWith(".py") ||
path.endsWith(".go") ||
path.endsWith(".sh")
) {
export async function handleFile(path: string, content: string, workspace: string, alreadySynced: string[]): Promise<boolean> {
if (path.endsWith(".ts") || path.endsWith(".py") || path.endsWith(".go") || path.endsWith(".sh")) {
if (alreadySynced.includes(path)) {
return true;
return true
}
alreadySynced.push(path);
alreadySynced.push(path)
const remotePath = path.substring(0, path.length - 3);
const metaPath = remotePath + ".script.json";
let typed = undefined;
let typed = undefined
try {
await Deno.stat(metaPath);
typed = JSON.parse(await Deno.readTextFile(metaPath));
await Deno.stat(metaPath)
typed = JSON.parse(await Deno.readTextFile(metaPath))
typed = decoverto.type(ScriptFile).plainToInstance(typed);
} catch {}
} catch { }
const language = inferContentTypeFromFilePath(path);
let remote = undefined;
try {
remote = await ScriptService.getScriptByPath({
const remote = await ScriptService.getScriptByPath({
workspace,
path: remotePath,
});
} catch {}
if (remote) {
if (content === remote.content) {
if (
typed == undefined ||
(typed.description === remote.description &&
typed.summary === remote.summary &&
typed.is_template === remote.is_template &&
typed.kind == remote.kind &&
remote?.lock == typed.lock?.join("\n") &&
JSON.stringify(typed.schema) == JSON.stringify(remote.schema))
) {
console.log(
colors.yellow(
`No change to push for script ${remotePath}, skipping`
)
);
return true;
}
}
await ScriptService.createScript({
workspace,
requestBody: {
content,
description: typed?.description ?? "",
description: typed.description,
language,
path: remotePath,
summary: typed?.summary ?? "",
is_template: typed?.is_template,
kind: typed?.kind,
lock: typed?.lock,
summary: typed.summary,
is_template: typed.is_template,
kind: typed.kind,
lock: typed.lock,
parent_hash: remote.hash,
schema: typed?.schema,
schema: typed.schema,
},
});
console.log(
colors.yellow.bold(`Creating script with a parent ${remotePath}`)
);
} else {
console.log(colors.yellow.bold(`Creating script with a parent ${remotePath}`))
} catch {
// no parent hash
await ScriptService.createScript({
workspace: workspace,
@@ -192,13 +143,12 @@ export async function handleFile(
schema: typed.schema,
},
});
console.log(
colors.yellow.bold(`Creating script without parent ${remotePath}`)
);
console.log(colors.yellow.bold(`Creating script without parent ${remotePath}`))
}
return true;
return true
}
return false;
return false
}
export async function findContentFile(filePath: string) {
@@ -217,7 +167,7 @@ export async function findContentFile(filePath: string) {
.then((e) => {
return { path: x, file: e };
});
})
}),
)
)
.filter((x) => x.file)
@@ -225,7 +175,7 @@ export async function findContentFile(filePath: string) {
if (validCandidates.length > 1) {
throw new Error(
"No content path given and more then one candidate found: " +
validCandidates.join(", ")
validCandidates.join(", "),
);
}
if (validCandidates.length < 1) {
@@ -235,7 +185,7 @@ export async function findContentFile(filePath: string) {
}
export function inferContentTypeFromFilePath(
contentPath: string
contentPath: string,
): "python3" | "deno" | "go" | "bash" {
let language = contentPath.substring(contentPath.lastIndexOf("."));
if (language == ".ts") language = "deno";
@@ -243,9 +193,7 @@ export function inferContentTypeFromFilePath(
if (language == ".sh") language = "bash";
if (language == ".go") language = "go";
if (
language != "python3" &&
language != "deno" &&
language != "go" &&
language != "python3" && language != "deno" && language != "go" &&
language != "bash"
) {
throw new Error("Invalid language: " + language);
@@ -257,11 +205,11 @@ export async function pushScript(
filePath: string,
contentPath: string,
workspace: string,
remotePath: string
remotePath: string,
) {
const data = decoverto
.type(ScriptFile)
.rawToInstance(await Deno.readTextFile(filePath));
const data = decoverto.type(ScriptFile).rawToInstance(
await Deno.readTextFile(filePath),
);
const content = await Deno.readTextFile(contentPath);
const language = inferContentTypeFromFilePath(contentPath);
@@ -322,7 +270,14 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
.header(["path", "summary", "language", "created by"])
.padding(2)
.border(true)
.body(total.map((x) => [x.path, x.summary, x.language, x.created_by]))
.body(
total.map((x) => [
x.path,
x.summary,
x.language,
x.created_by,
]),
)
.render();
}
@@ -333,15 +288,14 @@ export async function resolve(input: string): Promise<Record<string, any>> {
if (input == "@-") {
input = new TextDecoder().decode(await readAll(Deno.stdin));
}
if (input[0] == "@") {
} 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;
console.error("Impossible to parse input as JSON", input)
throw e
}
}
@@ -350,11 +304,12 @@ async function run(
data?: string;
silent: boolean;
},
path: string
path: string,
) {
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
const input = opts.data ? await resolve(opts.data) : {};
const id = await JobService.runScriptByPath({
workspace: workspace.workspaceId,
@@ -368,13 +323,12 @@ async function run(
while (true) {
try {
const result =
(
await JobService.getCompletedJob({
workspace: workspace.workspaceId,
id,
})
).result ?? {};
const result = (
await JobService.getCompletedJob({
workspace: workspace.workspaceId,
id,
})
).result ?? {};
console.log(result);
break;
@@ -389,9 +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()
console.log(colors.bold.underline.green("Job Completed"));
console.log();
console.log()
return;
} catch {
/* ignore */
@@ -442,7 +396,7 @@ export async function track_job(workspace: string, id: string) {
if (running && updates.running === false) {
running = false;
console.log(
colors.yellow("Job suspended. Waiting for it to continue...")
colors.yellow("Job suspended. Waiting for it to continue..."),
);
}
}
@@ -453,13 +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");
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();
console.log()
} catch {
console.log("Job appears to have completed, but no data can be retrieved");
}
@@ -484,7 +440,7 @@ const command = new Command()
.action(list as any)
.command(
"push",
"push a local script spec. This overrides any remote versions."
"push a local script spec. This overrides any remote versions.",
)
.arguments("<file_path:string> <remote_path:string> [content_path:string]")
.action(push as any)
@@ -495,11 +451,11 @@ const command = new Command()
.arguments("<path:string>")
.option(
"-d --data <data:string>",
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-."
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
)
.option(
"-s --silent",
"Do not ouput anything other then the final output. Useful for scripting."
"Do not ouput anything other then the final output. Useful for scripting.",
)
.action(run as any);

View File

@@ -13,7 +13,7 @@ import {
ResourceService,
VariableService,
AppService,
FlowService,
FlowService
} from "./deps.ts";
import {
Difference,
@@ -25,13 +25,16 @@ import {
import { downloadZip } from "./pull.ts";
import { FolderFile } from "./folder.ts";
import { ResourceTypeFile } from "./resource-type.ts";
import { handleScriptMetadata, ScriptFile } from "./script.ts";
import {
handleScriptMetadata,
ScriptFile,
} from "./script.ts";
import { ResourceFile } from "./resource.ts";
import { FlowFile } from "./flow.ts";
import { VariableFile } from "./variable.ts";
import { handleFile } from "./script.ts";
import { equal } from "https://deno.land/x/equal@v1.5.0/mod.ts";
import { diffCharacters } from "https://deno.land/x/diff@v0.3.5/mod.ts";
import { equal } from "https://deno.land/x/equal/mod.ts";
import { diffCharacters } from "https://deno.land/x/diff/mod.ts";
type DynFSElement = {
isDirectory: boolean;
path: string;
@@ -41,7 +44,10 @@ type DynFSElement = {
};
async function FSFSElement(p: string): Promise<DynFSElement> {
function _internal_element(localP: string, isDir: boolean): DynFSElement {
function _internal_element(
localP: string,
isDir: boolean,
): DynFSElement {
return {
isDirectory: isDir,
path: localP.substring(p.length + 1),
@@ -107,14 +113,16 @@ function ZipFSElement(zip: JSZip): DynFSElement {
async function* readDirRecursiveWithIgnore(
ignore: (path: string, isDirectory: boolean) => boolean,
root: DynFSElement
): AsyncGenerator<{
path: string;
ignored: boolean;
isDirectory: boolean;
getContentBytes(): Promise<Uint8Array>;
getContentText(): Promise<string>;
}> {
root: DynFSElement,
): AsyncGenerator<
{
path: string;
ignored: boolean;
isDirectory: boolean;
getContentBytes(): Promise<Uint8Array>;
getContentText(): Promise<string>;
}
> {
const stack: {
path: string;
isDirectory: boolean;
@@ -122,20 +130,18 @@ async function* readDirRecursiveWithIgnore(
c(): AsyncIterable<DynFSElement>;
getContentBytes(): Promise<Uint8Array>;
getContentText(): Promise<string>;
}[] = [
{
path: root.path,
ignored: ignore(root.path, root.isDirectory),
isDirectory: root.isDirectory,
c: root.getChildren,
getContentBytes(): Promise<Uint8Array> {
throw undefined;
},
getContentText(): Promise<string> {
throw undefined;
},
}[] = [{
path: root.path,
ignored: ignore(root.path, root.isDirectory),
isDirectory: root.isDirectory,
c: root.getChildren,
getContentBytes(): Promise<Uint8Array> {
throw undefined;
},
];
getContentText(): Promise<string> {
throw undefined;
},
}];
while (stack.length > 0) {
const e = stack.pop()!;
@@ -155,17 +161,17 @@ async function* readDirRecursiveWithIgnore(
}
type Added = { name: "added"; path: string; content: string };
type Deleted = { name: "deleted"; path: string };
type Edit = { name: "edited"; path: string; before: string; after: string };
type Deleted = { name: "deleted"; path: string; };
type Edit = { name: "edited"; path: string; before: string; after: string; };
type Change = Added | Deleted | Edit;
async function elementsToMap(
els: DynFSElement,
ignore: (path: string, isDirectory: boolean) => boolean
): Promise<{ [key: string]: string }> {
async function elementsToMap(els: DynFSElement, ignore: (path: string, isDirectory: boolean) => boolean): Promise<{ [key: string]: string }> {
const map: { [key: string]: string } = {};
for await (const entry of readDirRecursiveWithIgnore(ignore, els)) {
for await (const entry of readDirRecursiveWithIgnore(
ignore,
els,
)) {
if (entry.isDirectory || entry.ignored) continue;
const content = await entry.getContentText();
map[entry.path] = content;
@@ -173,26 +179,20 @@ async function elementsToMap(
return map;
}
async function compareDynFSElement(
els1: DynFSElement,
els2: DynFSElement | undefined,
ignore: (path: string, isDirectory: boolean) => boolean
els1: DynFSElement, els2: DynFSElement,
ignore: (path: string, isDirectory: boolean) => boolean,
raw: boolean
): Promise<Change[]> {
const [m1, m2] = els2
? await Promise.all([
elementsToMap(els1, ignore),
elementsToMap(els2, ignore),
])
: [await elementsToMap(els1, ignore), {}];
const [m1, m2] = raw ? [await elementsToMap(els1, ignore), {}] :
await Promise.all([elementsToMap(els1, ignore), elementsToMap(els2, ignore)]);
const changes: Change[] = [];
for (const [k, v] of Object.entries(m1)) {
if (m2[k] === undefined) {
changes.push({ name: "added", path: k, content: v });
} else if (
m2[k] != v &&
(!k.endsWith(".json") || !equal(JSON.parse(v), JSON.parse(m2[k])))
) {
} else if (m2[k] != v && (!k.endsWith(".json") || !equal(JSON.parse(v), JSON.parse(m2[k])))) {
// await Deno.writeTextFile("/tmp/k", m2[k])
// await Deno.writeTextFile("/tmp/v", v)
// console.log(k)
@@ -208,54 +208,54 @@ async function compareDynFSElement(
}
}
return changes;
return changes
}
const isNotWmillFile = (p: string, isDirectory: boolean) => {
if (p.endsWith("/")) {
return false;
return false
}
if (isDirectory) {
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/");
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
}
try {
const typ = getTypeStrFromPath(p);
if (typ == "resource-type") {
return p.includes("/");
const typ = getTypeStrFromPath(p)
if (typ == 'resource-type') {
return p.includes('/')
} else {
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/");
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
}
} catch {
return true;
return true
}
};
}
const isWhitelisted = (p: string) => {
return p == "./" || p == "" || p == "u" || p == "f" || p == "g";
};
return p == "./" || p == "" || p == "u" || p == "f" || p == "g"
}
async function ignoreF() {
try {
const ignore: {
accepts(file: string): boolean;
denies(file: string): boolean;
} = gitignore_parser.compile(await Deno.readTextFile(".wmillignore"));
} = gitignore_parser.compile(
await Deno.readTextFile(".wmillignore"),
);
return (p: string, isDirectory: boolean) => {
return (
!isWhitelisted(p) &&
(isNotWmillFile(p, isDirectory) || ignore.denies(p))
);
};
return !isWhitelisted(p) && (isNotWmillFile(p, isDirectory) || ignore.denies(p));
}
} catch (e) {
return (p: string, isDirectory: boolean) =>
!isWhitelisted(p) && isNotWmillFile(p, isDirectory);
return (p: string, isDirectory: boolean) => !isWhitelisted(p) && isNotWmillFile(p, isDirectory)
}
}
async function pull(
opts: GlobalOptions & { raw: boolean; yes: boolean; failConflicts: boolean }
opts: GlobalOptions & { raw: boolean; yes: boolean, failConflicts: boolean },
) {
if (!opts.raw) {
await ensureDir(path.join(Deno.cwd(), ".wmill"));
}
@@ -263,96 +263,74 @@ async function pull(
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
console.log(
colors.gray(
"Computing the files to update locally to match remote (taking .wmillignore into account)"
)
);
const remote = ZipFSElement((await downloadZip(workspace))!);
const local = opts.raw
? undefined
: await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"));
const changes = await compareDynFSElement(remote, local, await ignoreF());
console.log(colors.gray("Computing the files to update locally to match remote (taking .wmillignore into account)"));
const remote = ZipFSElement((await downloadZip(workspace))!)
const local = await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"))
const changes = await compareDynFSElement(remote, local, await ignoreF(), opts.raw)
console.log(`remote -> local: ${changes.length} changes to apply`);
if (changes.length > 0) {
prettyChanges(changes);
prettyChanges(changes)
if (
!opts.yes &&
!opts.raw &&
!(await Confirm.prompt({
message: `Do you want to apply these ${changes.length} changes?`,
default: true,
}))
!opts.yes && !opts.raw && !(await Confirm.prompt({ message: `Do you want to apply these ${changes.length} changes?`, default: true }))
) {
return;
return
}
const conflicts = [];
const conflicts = []
console.log(colors.gray(`Applying changes to files ...`));
for await (const change of changes) {
const target = path.join(Deno.cwd(), change.path);
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
if (change.name === "edited") {
try {
const currentLocal = await Deno.readTextFile(target);
if (currentLocal !== change.before && currentLocal !== change.after) {
console.log(
colors.red(
`Conflict detected on ${change.path}\nBoth local and remote have been modified.`
)
);
const currentLocal = await Deno.readTextFile(target)
if (currentLocal !== change.before) {
console.log(colors.red(`Conflict detected on ${change.path}\nBoth local and remote have been modified.`))
if (opts.failConflicts) {
conflicts.push({
local: currentLocal,
change,
path: change.path,
});
conflicts.push({ local: currentLocal, change, path: change.path })
continue;
} else if (opts.yes) {
console.log(
colors.red(
`Override local version with remote since --yes was passed and no --fail-conflicts.`
)
);
} else {
showConflict(change.path, currentLocal, change.after);
if (
await Confirm.prompt(
"Preserve local (push to change remote and avoid seeing this again)?"
)
) {
console.log(colors.red(`Override local version with remote since --yes was passed and no --fail-conflicts.`))
}
else {
showConflict(change.path, currentLocal, change.after)
if (await Confirm.prompt("Preserve local (push to change remote and avoid seeing this again)?")) {
continue;
}
}
}
} catch {}
} catch { }
if (change.path.endsWith(".json")) {
const diffs = microdiff(
JSON.parse(change.before),
JSON.parse(change.after),
{ cyclesFix: false }
);
const diffs =
microdiff(
JSON.parse(change.before),
JSON.parse(change.after),
{ cyclesFix: false },
)
console.log(
`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`
console.log(`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`)
await applyDiff(
diffs,
target,
);
await applyDiff(diffs, target);
} else {
console.log(`Editing script ${change.path}`);
console.log(`Editing script ${change.path}`)
await Deno.writeTextFile(target, change.after);
}
if (!opts.raw) {
await ensureDir(path.dirname(stateTarget));
await ensureDir(path.dirname(stateTarget))
await Deno.copyFile(target, stateTarget);
}
} else if (change.name === "added") {
await ensureDir(path.dirname(target));
await ensureDir(path.dirname(target))
if (!opts.raw) {
await ensureDir(path.dirname(stateTarget));
console.log(
`Adding ${getTypeStrFromPath(change.path)} ${change.path}`
);
await ensureDir(path.dirname(stateTarget))
console.log(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`)
}
await Deno.writeTextFile(target, change.content);
if (!opts.raw) {
@@ -360,10 +338,8 @@ async function pull(
}
} else if (change.name === "deleted") {
try {
console.log(
`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`
);
await Deno.remove(target);
console.log(`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`)
await Deno.remove(target)
if (!opts.raw) {
await Deno.remove(stateTarget);
}
@@ -376,29 +352,24 @@ async function pull(
}
if (opts.failConflicts) {
if (conflicts.length > 0) {
console.error(colors.red(`Conflicts were found`));
console.log("Conflicts:");
console.error(colors.red(`Conflicts were found`))
console.log("Conflicts:")
for (const conflict of conflicts) {
showConflict(conflict.path, conflict.local, conflict.change.after);
showConflict(conflict.path, conflict.local, conflict.change.after)
}
console.log(
colors.red(`Please resolve theses conflicts manually by either:
console.log(colors.red(`Please resolve theses conflicts manually by either:
- reverting the content back to its remote (\`wmill pull\` and refuse to preserve local when prompted)
- pushing the changes with \`wmill push --skip-pull\` to override wmill with all your local changes
`)
);
Deno.exit(1);
`))
Deno.exit(1)
}
}
console.log(
colors.green.underline(
`Done! All ${changes.length} changes applied locally.`
)
);
console.log(colors.green.underline(`Done! All ${changes.length} changes applied locally.`));
}
function showConflict(path: string, local: string, remote: string) {
console.log(colors.yellow(`- ${path}`));
console.log(colors.yellow(`- ${path}`))
let finalString = "";
for (const character of diffCharacters(local, remote)) {
@@ -414,8 +385,8 @@ async function pull(
}
}
console.log(finalString);
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m");
console.log();
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m")
console.log()
}
async function applyDiff(diffs: Difference[], file: string) {
ensureDir(path.dirname(file));
@@ -444,20 +415,17 @@ async function pull(
}
}
function prettyChanges(changes: Change[]) {
for (const change of changes) {
if (change.name === "added") {
console.log(
colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path)
);
console.log(colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path));
} else if (change.name === "deleted") {
console.log(
colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path)
);
console.log(colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path));
} else if (change.name === "edited") {
console.log(
colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path)
);
console.log(colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path));
}
}
}
@@ -484,208 +452,138 @@ function prettyDiff(diffs: Difference[]) {
}
}
function removeSuffix(str: string, suffix: string) {
return str.slice(0, str.length - suffix.length);
}
async function push(
opts: GlobalOptions & {
raw: boolean;
yes: boolean;
skipPull: boolean;
failConflicts: boolean;
}
) {
async function push(opts: GlobalOptions & { raw: boolean, yes: boolean, skipPull: boolean, failConflicts: boolean }) {
if (!opts.raw) {
if (!opts.skipPull) {
console.log(
colors.gray("You need to be up-to-date before pushing, pulling first.")
);
await pull(opts);
console.log(colors.green("Pull done, now pushing."));
console.log();
console.log(colors.gray("You need to be up-to-date before pushing, pulling first."))
await pull(opts)
console.log(colors.green("Pull done, now pushing."))
console.log()
}
}
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
console.log(
colors.gray(
"Computing the files to update on the remote to match local (taking .wmillignore into account)"
)
);
const remote = opts.raw
? undefined
: ZipFSElement((await downloadZip(workspace))!);
const local = await FSFSElement(path.join(Deno.cwd(), ""));
const changes = await compareDynFSElement(local, remote, await ignoreF());
console.log(colors.gray("Computing the files to update on the remote to match local (taking .wmillignore into account)"));
const remote = ZipFSElement((await downloadZip(workspace))!)
const local = await FSFSElement(path.join(Deno.cwd(), ""))
const changes = await compareDynFSElement(local, remote, await ignoreF(), opts.raw)
console.log(`remote <- local: ${changes.length} changes to apply`);
if (changes.length > 0) {
prettyChanges(changes);
prettyChanges(changes)
if (
!opts.yes &&
!(await Confirm.prompt({
message: `Do you want to apply these ${changes.length} changes?`,
default: true,
}))
!opts.yes && !(await Confirm.prompt({ message: `Do you want to apply these ${changes.length} changes?`, default: true }))
) {
return;
return
}
console.log(colors.gray(`Applying changes to files ...`));
const alreadySynced: string[] = [];
const alreadySynced: string[] = []
for await (const change of changes) {
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
if (change.name === "edited") {
if (
await handleScriptMetadata(
change.path,
workspace.workspaceId,
alreadySynced
)
) {
if (await handleScriptMetadata(change.path, workspace.workspaceId, alreadySynced)) {
if (!opts.raw) {
await Deno.writeTextFile(stateTarget, change.after);
}
continue;
} else if (
await handleFile(
change.path,
change.after,
workspace.workspaceId,
alreadySynced
)
) {
continue
} else if (await handleFile(change.path, change.after, workspace.workspaceId, alreadySynced)) {
if (!opts.raw) {
await Deno.writeTextFile(stateTarget, change.after);
}
continue;
continue
}
if (!opts.raw) {
await ensureDir(path.dirname(stateTarget));
console.log(
`Editing ${getTypeStrFromPath(change.path)} ${change.path}`
);
await ensureDir(path.dirname(stateTarget))
console.log(`Editing ${getTypeStrFromPath(change.path)} ${change.path}`)
}
const obj = inferTypeFromPath(change.path, JSON.parse(change.after));
const obj = inferTypeFromPath(change.path, JSON.parse(change.after))
const diff = microdiff(
inferTypeFromPath(change.path, JSON.parse(change.before)),
obj,
{ cyclesFix: false }
);
const diff = microdiff(inferTypeFromPath(change.path, JSON.parse(change.before)), obj, { cyclesFix: false });
await applyDiff(
workspace.workspaceId,
change.path.split(".")[0],
obj,
diff
diff,
);
if (!opts.raw) {
await Deno.writeTextFile(stateTarget, change.after);
}
} else if (change.name === "added") {
if (change.path.endsWith(".script.json")) {
continue;
} else if (
await handleFile(
change.path,
change.content,
workspace.workspaceId,
alreadySynced
)
) {
continue;
continue
} else if (await handleFile(change.path, change.content, workspace.workspaceId, alreadySynced)) {
continue
}
if (!opts.raw) {
await ensureDir(path.dirname(stateTarget));
console.log(
`Adding ${getTypeStrFromPath(change.path)} ${change.path}`
);
await ensureDir(path.dirname(stateTarget))
console.log(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`)
}
const obj = inferTypeFromPath(change.path, JSON.parse(change.content));
const obj = inferTypeFromPath(change.path, JSON.parse(change.content))
const diff = microdiff({}, obj, { cyclesFix: false });
await applyDiff(
workspace.workspaceId,
change.path.split(".")[0],
obj,
diff
diff,
);
if (!opts.raw) {
await Deno.writeTextFile(stateTarget, change.content);
}
} else if (change.name === "deleted") {
if (!change.path.includes(".json")) {
continue;
continue
}
console.log(
`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`
);
const typ = getTypeStrFromPath(change.path);
console.log(`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`)
const typ = getTypeStrFromPath(change.path)
const workspaceId = workspace.workspaceId;
switch (typ) {
case "script": {
const script = await ScriptService.getScriptByPath({
workspace: workspaceId,
path: removeSuffix(change.path, ".script.json"),
});
await ScriptService.deleteScriptByHash({
workspace: workspaceId,
hash: script.hash,
});
const script = await ScriptService.getScriptByPath({ workspace: workspaceId, path: removeSuffix(change.path, ".script.json") })
await ScriptService.deleteScriptByHash({ workspace: workspaceId, hash: script.hash })
break;
}
case "folder":
await FolderService.deleteFolder({
workspace: workspaceId,
name: change.path.split("/")[1],
});
await FolderService.deleteFolder({ workspace: workspaceId, name: change.path.split('/')[1] })
break;
case "resource":
await ResourceService.deleteResource({
workspace: workspaceId,
path: removeSuffix(change.path, ".resource.json"),
});
await ResourceService.deleteResource({ workspace: workspaceId, path: removeSuffix(change.path, ".resource.json") })
break;
case "resource-type":
await ResourceService.deleteResourceType({
workspace: workspaceId,
path: removeSuffix(change.path, ".resource-type.json"),
});
break;
await ResourceService.deleteResourceType({ workspace: workspaceId, path: removeSuffix(change.path, ".resource-type.json") })
break
case "flow":
await FlowService.deleteFlowByPath({
workspace: workspaceId,
path: removeSuffix(change.path, ".flow.json"),
});
break;
await FlowService.deleteFlowByPath({ workspace: workspaceId, path: removeSuffix(change.path, ".flow.json") })
break
case "app":
await AppService.deleteApp({
workspace: workspaceId,
path: removeSuffix(change.path, ".app.json"),
});
break;
await AppService.deleteApp({ workspace: workspaceId, path: removeSuffix(change.path, ".app.json") })
break
case "variable":
await VariableService.deleteVariable({
workspace: workspaceId,
path: removeSuffix(change.path, ".variable.json"),
});
break;
await VariableService.deleteVariable({ workspace: workspaceId, path: removeSuffix(change.path, ".variable.json") })
break
default:
break;
}
try {
await Deno.remove(stateTarget);
} catch {}
Deno.remove(stateTarget)
} catch { }
}
}
console.log(
colors.green.underline(
`Done! All ${changes.length} changes pushed to the remote workspace.`
)
);
console.log(colors.green.underline(`Done! All ${changes.length} changes pushed to the remote workspace.`));
}
async function applyDiff(
workspace: string,
remotePath: string,
@@ -696,11 +594,11 @@ async function push(
| ResourceFile
| ResourceTypeFile
| FolderFile,
diffs: Difference[]
diffs: Difference[],
) {
if (file instanceof ScriptFile) {
throw new Error(
"This code path should be unreachable - we should never generate diffs for scripts"
"This code path should be unreachable - we should never generate diffs for scripts",
);
} else if (file instanceof FolderFile) {
const parts = remotePath.split("/");
@@ -711,14 +609,14 @@ async function push(
}
}
if (diffs.length === 0) {
console.log("No diffs to apply to " + remotePath);
console.log("No diffs to apply to " + remotePath)
return;
}
try {
await file.pushDiffs(workspace, remotePath, diffs);
} catch (e) {
console.error("Failing to apply diffs to " + remotePath);
console.error(JSON.stringify(e));
console.error("Failing to apply diffs to " + remotePath)
console.error(e.body)
}
}
}
@@ -726,24 +624,18 @@ async function push(
const command = new Command()
.command("pull")
.description(
"Pull any remote changes and apply them locally. Use --raw for usage without local state tracking."
)
.option(
"--fail-conflicts",
"Error on conflicts (both remote and local have changes on the same item)"
"Pull any remote changes and apply them locally. Use --raw for usage without local state tracking.",
)
.option("--fail-conflicts", "Error on conflicts (both remote and local have changes on the same item)")
.option("--yes", "Pull without needing confirmation")
.option("--raw", "Pull without using state, just overwrite.")
.action(pull as any)
.command("push")
.description(
"Push any local changes and apply them remotely. Use --raw for usage without local state tracking."
"Push any local changes and apply them remotely. Use --raw for usage without local state tracking.",
)
.option(
"--fail-conflicts",
"Error on conflicts (both remote and local have changes on the same item)"
)
.option("--skip-pull", "Push without pulling first (you have pulled prior)")
.option("--fail-conflicts", "Error on conflicts (both remote and local have changes on the same item)")
.option("--skip-pull", "Push without pulling first")
.option("--yes", "Push without needing confirmation")
.option("--raw", "Push without using state, just overwrite.")
.action(push as any);

View File

@@ -11,6 +11,7 @@ import {
colors,
Command,
EditVariable,
ListableVariable,
microdiff,
Table,
VariableService,
@@ -66,7 +67,7 @@ export class VariableFile implements Resource, PushDiffs {
if (await VariableService.existsVariable({ workspace, path: remotePath })) {
console.log(
colors.bold.yellow(
`Applying ${diffs.length} diffs to existing variable... ${remotePath}`,
`Applying ${diffs.length} diffs to existing variable...`,
),
);
const changeset: EditVariable = {};
@@ -75,12 +76,11 @@ export class VariableFile implements Resource, PushDiffs {
diff.type !== "REMOVE" &&
(
diff.path.length !== 1 ||
!["path", "value", "is_secret", "description", "account", "is_oauth"].includes(
!["path", "value", "is_secret", "description"].includes(
diff.path[0] as string,
)
)
) {
console.log(colors.red("Invalid variable diff with path " + diff.path));
throw new Error("Invalid variable diff with path " + diff.path);
}
if (diff.type === "CREATE" || diff.type === "CHANGE") {
@@ -96,7 +96,6 @@ export class VariableFile implements Resource, PushDiffs {
if (!hasChanges) {
return;
}
await VariableService.updateVariable({
workspace,
path: remotePath,
@@ -104,6 +103,7 @@ export class VariableFile implements Resource, PushDiffs {
requestBody: changeset,
});
console.log(changeset);
} else {
console.log(colors.yellow.bold("Creating new variable..."));
await VariableService.createVariable({
@@ -121,10 +121,19 @@ export class VariableFile implements Resource, PushDiffs {
}
}
async push(workspace: string, remotePath: string): Promise<void> {
let existing: ListableVariable | undefined;
try {
existing = await VariableService.getVariable({
workspace: workspace,
path: remotePath,
});
} catch {
existing = undefined;
}
await this.pushDiffs(
workspace,
remotePath,
microdiff({}, this, { cyclesFix: false }),
microdiff(existing ?? {}, this, { cyclesFix: false }),
);
}
}

View File

@@ -1,59 +1,41 @@
import {
JobService,
ResourceService,
VariableService,
} from "./windmill-api/index.ts";
import { OpenAPI } from "./windmill-api/index.ts";
import { JobService, ResourceService, VariableService } from './windmill-api/index.ts'
import { OpenAPI } from './windmill-api/index.ts'
export {
AdminService,
AuditService,
FlowService,
GranularAclService,
GroupService,
JobService,
ResourceService,
VariableService,
ScriptService,
ScheduleService,
SettingsService,
UserService,
WorkspaceService,
} from "./windmill-api/index.ts";
AdminService, AuditService, FlowService, GranularAclService, GroupService,
JobService, ResourceService, VariableService, ScriptService, ScheduleService, SettingsService,
UserService, WorkspaceService
} from './windmill-api/index.ts'
// @ts-ignore: Otherwise BigInt is not supported for export
BigInt.prototype.toJSON = function () {
return this.toString();
return this.toString();
};
export { pgSql, pgClient } from "./pg.ts";
export { pgSql, pgClient } from './pg.ts'
export type Sql = string;
export type Email = string;
export type Base64 = string;
export type Resource<S extends string> = any;
export type Sql = string
export type Email = string
export type Base64 = string
export type Resource<S extends string> = any
export const SHARED_FOLDER = "/shared";
export const SHARED_FOLDER = '/shared'
export function setClient(token: string, baseUrl: string) {
OpenAPI.WITH_CREDENTIALS = true;
OpenAPI.TOKEN = token;
OpenAPI.BASE = baseUrl + "/api";
OpenAPI.WITH_CREDENTIALS = true
OpenAPI.TOKEN = token
OpenAPI.BASE = baseUrl + '/api'
}
setClient(
Deno.env.get("WM_TOKEN") ?? "no_token",
Deno.env.get("BASE_INTERNAL_URL") ??
Deno.env.get("BASE_URL") ??
"http://localhost:8000"
);
setClient(Deno.env.get("WM_TOKEN") ?? 'no_token', Deno.env.get("BASE_INTERNAL_URL") ?? Deno.env.get("BASE_URL") ?? 'http://localhost:8000')
/**
* Create a client configuration from env variables
* @returns client configuration
*/
export function getWorkspace(): string {
return Deno.env.get("WM_WORKSPACE") ?? "no_workspace";
return Deno.env.get("WM_WORKSPACE") ?? 'no_workspace'
}
/**
@@ -62,30 +44,27 @@ export function getWorkspace(): string {
* @param undefinedIfEmpty if the resource does not exist, return undefined instead of throwing an error
* @returns resource value
*/
export async function getResource(
path?: string,
undefinedIfEmpty?: boolean
): Promise<any> {
const workspace = getWorkspace();
path = path ?? getStatePath();
try {
const resource = await ResourceService.getResource({ workspace, path });
return await _transformLeaf(resource.value);
} catch (e: any) {
if (undefinedIfEmpty && e.status === 404) {
return undefined;
} else {
throw Error(`Resource not found at ${path} or not visible to you`);
export async function getResource(path?: string, undefinedIfEmpty?: boolean): Promise<any> {
const workspace = getWorkspace()
path = path ?? getStatePath()
try {
const resource = await ResourceService.getResource({ workspace, path })
return await _transformLeaf(resource.value)
} catch (e: any) {
if (undefinedIfEmpty && e.status === 404) {
return undefined
} else {
throw Error(`Resource not found at ${path} or not visible to you`)
}
}
}
}
export function getStatePath(): string {
const state_path = Deno.env.get("WM_STATE_PATH");
if (state_path === undefined) {
throw Error("State path not set");
}
return state_path;
const state_path = Deno.env.get("WM_STATE_PATH")
if (state_path === undefined) {
throw Error("State path not set")
}
return state_path
}
/**
@@ -94,29 +73,16 @@ export function getStatePath(): string {
* @param value new value of the resource to set
* @param initializeToTypeIfNotExist if the resource does not exist, initialize it with this type
*/
export async function setResource(
value: any,
path?: string,
initializeToTypeIfNotExist?: string
): Promise<void> {
path = path ?? getStatePath();
const workspace = getWorkspace();
if (await ResourceService.existsResource({ workspace, path })) {
await ResourceService.updateResourceValue({
workspace,
path,
requestBody: { value },
});
} else if (initializeToTypeIfNotExist) {
await ResourceService.createResource({
workspace,
requestBody: { path, value, resource_type: initializeToTypeIfNotExist },
});
} else {
throw Error(
`Resource at path ${path} does not exist and no type was provided to initialize it`
);
}
export async function setResource(value: any, path?: string, initializeToTypeIfNotExist?: string): Promise<void> {
path = path ?? getStatePath()
const workspace = getWorkspace()
if (await ResourceService.existsResource({ workspace, path })) {
await ResourceService.updateResourceValue({ workspace, path, requestBody: { value } })
} else if (initializeToTypeIfNotExist) {
await ResourceService.createResource({ workspace, requestBody: { path, value, resource_type: initializeToTypeIfNotExist } })
} else {
throw Error(`Resource at path ${path} does not exist and no type was provided to initialize it`)
}
}
/**
@@ -125,7 +91,7 @@ export async function setResource(
* @deprecated use setState instead
*/
export async function setInternalState(state: any): Promise<void> {
await setResource(state, undefined, "state");
await setResource(state, undefined, 'state')
}
/**
@@ -133,41 +99,39 @@ export async function setInternalState(state: any): Promise<void> {
* @param state state to set
*/
export async function setState(state: any): Promise<void> {
await setResource(state, undefined, "state");
await setResource(state, undefined, 'state')
}
/**
* Set the shared state
* @param state state to set
*/
export async function setSharedState(
state: any,
path = "state.json"
): Promise<void> {
await Deno.writeTextFile(SHARED_FOLDER + "/" + path, JSON.stringify(state));
export async function setSharedState(state: any, path = 'state.json'): Promise<void> {
await Deno.writeTextFile(SHARED_FOLDER + '/' + path, JSON.stringify(state))
}
/**
* Get the shared state
* @param state state to set
*/
export async function getSharedState(path = "state.json"): Promise<any> {
return JSON.parse(await Deno.readTextFile(SHARED_FOLDER + "/" + path));
export async function getSharedState(path = 'state.json'): Promise<any> {
return JSON.parse(await Deno.readTextFile(SHARED_FOLDER + '/' + path))
}
/**
* Get the internal state
* @deprecated use getState instead
*/
export async function getInternalState(): Promise<any> {
return await getResource(getStatePath(), true);
return await getResource(getStatePath(), true)
}
/**
* Get the state shared across executions
*/
export async function getState(): Promise<any> {
return await getResource(getStatePath(), true);
return await getResource(getStatePath(), true)
}
/**
@@ -176,43 +140,38 @@ export async function getState(): Promise<any> {
* @returns variable value
*/
export async function getVariable(path: string): Promise<string | undefined> {
const workspace = getWorkspace();
try {
const variable = await VariableService.getVariable({ workspace, path });
return variable.value;
} catch (e: any) {
throw Error(`Variable not found at ${path} or not visible to you`);
}
const workspace = getWorkspace()
try {
const variable = await VariableService.getVariable({ workspace, path })
return variable.value
} catch (e: any) {
throw Error(`Variable not found at ${path} or not visible to you`)
}
}
async function transformLeaves(d: {
[key: string]: any;
}): Promise<{ [key: string]: any }> {
for (const k in d) {
d[k] = await _transformLeaf(d[k]);
}
return d;
async function transformLeaves(d: { [key: string]: any }): Promise<{ [key: string]: any }> {
for (const k in d) {
d[k] = await _transformLeaf(d[k])
}
return d
}
const VAR_RESOURCE_PREFIX = "$var:";
const RES_RESOURCE_PREFIX = "$res:";
const VAR_RESOURCE_PREFIX = "$var:"
async function _transformLeaf(v: any): Promise<any> {
if (typeof v === "object") {
return transformLeaves(v);
} else if (typeof v === "string" && v.startsWith(VAR_RESOURCE_PREFIX)) {
const varName = v.substring(VAR_RESOURCE_PREFIX.length);
return await getVariable(varName);
} else if (typeof v === "string" && v.startsWith(RES_RESOURCE_PREFIX)) {
const resName = v.substring(RES_RESOURCE_PREFIX.length);
return await getResource(resName);
} else {
return v;
}
if (typeof v === 'object') {
return transformLeaves(v)
}
else if (typeof v === 'string' && v.startsWith(VAR_RESOURCE_PREFIX)) {
const varName = v.substring(VAR_RESOURCE_PREFIX.length)
return await getVariable(varName)
} else {
return v
}
}
export async function databaseUrlFromResource(path: string): Promise<string> {
const resource = await getResource(path);
return `postgresql://${resource.user}:${resource.password}@${resource.host}:${resource.port}/${resource.dbname}?sslmode=${resource.sslmode}`;
const resource = await getResource(path)
return `postgresql://${resource.user}:${resource.password}@${resource.host}:${resource.port}/${resource.dbname}?sslmode=${resource.sslmode}`
}
/**
@@ -221,84 +180,78 @@ export async function databaseUrlFromResource(path: string): Promise<string> {
* @returns approval page UI URL, resume and cancel API URLs for resumeing the flow
*/
export async function getResumeUrls(approver?: string): Promise<{
approvalPage: string;
resume: string;
cancel: string;
approvalPage: string;
resume: string;
cancel: string;
}> {
const nonce = Math.floor(Math.random() * 4294967295);
const workspace = getWorkspace();
return await JobService.getResumeUrls({
workspace,
resumeId: nonce,
approver,
id: Deno.env.get("WM_JOB_ID") ?? "NO_JOB_ID",
});
const nonce = Math.floor(Math.random() * 4294967295);
const workspace = getWorkspace()
return await JobService.getResumeUrls({ workspace, resumeId: nonce, approver, id: Deno.env.get("WM_JOB_ID") ?? 'NO_JOB_ID' })
}
/**
* @deprecated use getResumeUrls instead
*/
export function getResumeEndpoints(approver?: string): Promise<{
approvalPage: string;
resume: string;
cancel: string;
approvalPage: string;
resume: string;
cancel: string;
}> {
return getResumeUrls(approver);
return getResumeUrls(approver)
}
export function base64ToUint8Array(data: string): Uint8Array {
return Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
return Uint8Array.from(atob(data), c => c.charCodeAt(0))
}
export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string {
let base64 = "";
const encodings =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let base64 = ''
const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const bytes = new Uint8Array(arrayBuffer);
const byteLength = bytes.byteLength;
const byteRemainder = byteLength % 3;
const mainLength = byteLength - byteRemainder;
const bytes = new Uint8Array(arrayBuffer)
const byteLength = bytes.byteLength
const byteRemainder = byteLength % 3
const mainLength = byteLength - byteRemainder
let a, b, c, d;
let chunk;
let a, b, c, d
let chunk
// Main loop deals with bytes in chunks of 3
for (let i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
// Main loop deals with bytes in chunks of 3
for (let i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
d = chunk & 63; // 63 = 2^6 - 1
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
d = chunk & 63 // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
}
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength];
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength]
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4 // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + "==";
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
base64 += encodings[a] + encodings[b] + '=='
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2 // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + "=";
}
base64 += encodings[a] + encodings[b] + encodings[c] + '='
}
return base64;
return base64
}

View File

@@ -1 +0,0 @@
VITE_DEFAULT_WEBHOOK_TYPE=async

1
frontend/.gitignore vendored
View File

@@ -7,4 +7,3 @@ node_modules
CaddyfileRemoteRuben
tests-out/
storageState.json
.env.production

View File

@@ -17,7 +17,7 @@ In the root folder:
```bash
docker build . -t windmill
docker compose up db windmill_server windmill_worker
docker-compose up db windmill_server windmill_worker
```
### 2. Backend is run by cargo
@@ -145,25 +145,3 @@ Recommended config for VS Code:
```
- turn _format on save_ on
## Building
The project is built with [SvelteKit](https://kit.svelte.dev/) and uses as output static files.
There are others adapters for sveltekit, but we use the static adapter.
To build the frontend as static assets, use:
```
npm run build
```
The output is in the `build` folder.
The default build assume you serve every non static files as the 200.html file which is catchall. If you prefer a normal layout, you can use:
```
NOTCATCHALL=true npm run build
```
which will generate an index.html and allow you to serve the frontend with any static server.
Env variables used for build are set in .env file. See [https://vitejs.dev/guide/env-and-mode.html#env-files](https://vitejs.dev/guide/env-and-mode.html#env-files) for more details.

View File

@@ -1,12 +1,12 @@
{
"name": "windmill",
"version": "1.79.0",
"version": "1.74.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill",
"version": "1.79.0",
"version": "1.74.2",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
@@ -23,7 +23,6 @@
"date-fns": "^2.29.3",
"fast-equals": "^4.0.3",
"highlight.js": "^11.7.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.115.0",
"monaco-editor": "0.33.0",
"monaco-editor-workers": "0.33.0",
@@ -45,11 +44,11 @@
"@tailwindcss/typography": "^0.5.8",
"@types/d3": "^7.4.0",
"@types/d3-zoom": "^3.0.2",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.18",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.6",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
"cssnano": "^5.1.14",
@@ -65,6 +64,7 @@
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"shepherd.js": "^10.0.1",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
"svelte": "^3.55.1",
@@ -1287,12 +1287,6 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"node_modules/@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"node_modules/@types/minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
@@ -1608,6 +1602,12 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@windmill-labs/svelte-grid": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
"dev": true
},
"node_modules/@zeit/schemas": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.21.0.tgz",
@@ -4409,11 +4409,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
@@ -6659,6 +6654,24 @@
"node": ">=8"
}
},
"node_modules/shepherd.js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.11.5",
"deepmerge": "^4.2.2",
"smoothscroll-polyfill": "^0.4.4"
},
"engines": {
"node": "12.* || 14.* || >= 16"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/rwwagner90"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -6745,6 +6758,12 @@
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/smoothscroll-polyfill": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
"dev": true
},
"node_modules/sorcery": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
@@ -9095,12 +9114,6 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"@types/minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz",
@@ -9296,6 +9309,12 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"@windmill-labs/svelte-grid": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
"dev": true
},
"@zeit/schemas": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.21.0.tgz",
@@ -11377,11 +11396,6 @@
"p-locate": "^5.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
@@ -13000,6 +13014,17 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"shepherd.js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
"dev": true,
"requires": {
"@popperjs/core": "^2.11.5",
"deepmerge": "^4.2.2",
"smoothscroll-polyfill": "^0.4.4"
}
},
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -13060,6 +13085,12 @@
"is-fullwidth-code-point": "^3.0.0"
}
},
"smoothscroll-polyfill": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
"dev": true
},
"sorcery": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.79.0",
"version": "1.74.2",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -23,11 +23,11 @@
"@tailwindcss/typography": "^0.5.8",
"@types/d3": "^7.4.0",
"@types/d3-zoom": "^3.0.2",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.18",
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.6",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
"cssnano": "^5.1.14",
@@ -43,6 +43,7 @@
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"shepherd.js": "^10.0.1",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
"svelte": "^3.55.1",
@@ -79,7 +80,6 @@
"date-fns": "^2.29.3",
"fast-equals": "^4.0.3",
"highlight.js": "^11.7.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.115.0",
"monaco-editor": "0.33.0",
"monaco-editor-workers": "0.33.0",

View File

@@ -1,2 +1,79 @@
/// <reference types="@sveltejs/kit" />
declare type Item = import('svelte-dnd-action').Item
declare type DndEvent<ItemType = Item> = import('svelte-dnd-action').DndEvent<ItemType>
declare namespace svelte.JSX {
interface HTMLAttributes<T> {
onconsider?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void
onfinalize?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void
}
}
declare module '@windmill-labs/svelte-grid' {
import type { SvelteComponentTyped } from 'svelte'
export interface Size {
w: number
h: number
}
export interface Positon {
x: number
y: number
}
interface ItemLayout extends Size, Positon {
fixed?: boolean
resizable?: boolean
draggable?: boolean
customDragger?: boolean
customResizer?: boolean
min?: Size
max?: Size
}
export type Item<T> = T & { [width: number]: ItemLayout; data: any }
export type FilledItem<T> = T & { [width: number]: Required<ItemLayout>; data: any }
export interface Props<T> {
fillSpace?: boolean
items: FilledItem<T>[]
rowHeight: number
cols: [number, number][]
gap?: [number, number]
fastStart?: boolean
throttleUpdate?: number
throttleResize?: number
onTopId?: string
scroller?: undefined
sensor?: number
parentWidth?: number
}
export interface Slots<T> {
default: { item: ItemLayout; dataItem: Item<T> }
}
export default class Grid<T = {}> extends SvelteComponentTyped<
Props<T>,
{
pointerup: CustomEvent<{ id: string }>
mount: CustomEvent<>
},
Slots<T>
> { }
}
declare module '@windmill-labs/svelte-grid/build/helper/index.mjs' {
import { ItemLayout } from '@windmill-labs/svelte-grid'
const x: {
normalize(items: any[], col: any): unknown[]
adjust(items: any[], col: any): unknown[]
findSpace(item: any, items: any, cols: any): unknown
item<T>(obj: ItemLayout): Required<ItemLayout>
}
export default x
}

View File

@@ -81,7 +81,7 @@
$: key =
apiTokenApps[resource_type]?.key ??
(args != undefined
? Object.keys(args).filter((x) => ['token', 'password', 'api_key', 'key'].includes(x))[0]
? Object.keys(args).filter((x) => ['token', 'password', 'api_key'].includes(x))[0]
: undefined)
let scopes: string[] = []
@@ -259,7 +259,6 @@
args['token'] == '' &&
args['password'] == '' &&
args['api_key'] == '' &&
args['key'] == '' &&
key != undefined) ||
(step == 3 && pathError != '') ||
!isValid
@@ -397,7 +396,7 @@
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder={resource_type}
namePlaceholder="{resource_type}"
kind="resource"
/>
@@ -434,7 +433,7 @@
{:else}
<Path
initialPath=""
namePlaceholder={resource_type}
namePlaceholder="{resource_type}"
bind:error={pathError}
bind:path
kind="resource"

View File

@@ -7,7 +7,7 @@
faPlus
} from '@fortawesome/free-solid-svg-icons'
import { setInputCat as computeInputCat } from '$lib/utils'
import { setInputCat as computeInputCat, type InputCat } from '$lib/utils'
import { Badge, Button } from './common'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
@@ -152,8 +152,6 @@
value = enum_?.[0]
} else if (inputCat == 'boolean') {
value = false
} else if (inputCat == 'list') {
value = []
}
}
}
@@ -290,7 +288,7 @@
on:click={() => {
value = value.filter((el) => el != v)
if (value.length == 0) {
value = []
value = undefined
}
}}
>
@@ -452,6 +450,19 @@
{/if}
</div>
{/if}
{#if !required && inputCat != 'resource-object'}
<!-- <Tooltip placement="bottom" content="Reset to default value">
<Button
on:click={() => (value = undefined)}
{disabled}
color="alternative"
size="sm"
class="h-8"
>
<Icon data={faArrowRotateLeft} />
</Button>
</Tooltip> -->
{/if}
<slot name="actions" />
</div>
{#if !compact || (error && error != '')}

View File

@@ -248,6 +248,6 @@
<Highlight language={json} code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')} />
{/if}
{:else}
<div class="text-gray-500 text-sm">No result: {JSON.stringify(result)}</div>
<div class="text-gray-500 text-sm">No result</div>
{/if}
</div>

View File

@@ -2,10 +2,9 @@
import type { DropdownItem } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { MoreVertical } from 'lucide-svelte'
import { MoreHorizontal } from 'lucide-svelte'
import { Button, Menu } from './common'
import { goto } from '$app/navigation'
import { twMerge } from 'tailwind-merge'
type Alignment = 'start' | 'end'
type Side = 'top' | 'bottom'
@@ -14,24 +13,15 @@
export let dropdownItems: DropdownItem[]
export let name: string | undefined = undefined
export let placement: Placement = 'bottom-start'
export let btnClasses = ''
$: buttonClass = twMerge('!border-0 bg-transparent !p-[6px]', btnClasses)
export let btnClasses = '!text-blue-500 bg-transparent'
const dispatch = createEventDispatcher()
</script>
<Menu {placement} let:close>
<Button
nonCaptureEvent
color="dark"
variant="border"
size="xs"
btnClasses={buttonClass}
{...$$restProps}
slot="trigger"
>
<Button nonCaptureEvent color="light" size="xs" {btnClasses} {...$$restProps} slot="trigger">
{#if !$$slots.default}
<MoreVertical size={20} />
<MoreHorizontal size={20} />
{:else}
<slot />
{/if}

View File

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

View File

@@ -20,6 +20,7 @@
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
import type { FlowEditorContext } from './flows/types'
import { cleanInputs } from './flows/utils'
import { Tour } from './tutorial'
export let initialPath: string = ''
export let selectedId: string | undefined
@@ -27,6 +28,7 @@
export let loading = false
export let flowStore: Writable<Flow>
export let flowStateStore: Writable<FlowState>
export let tour = false
async function createSchedule(path: string) {
const { cron, args, enabled } = $scheduleStore
@@ -246,6 +248,10 @@
<svelte:window on:keydown={onKeyDown} />
{#if tour}
<Tour tutorial="flow" />
{/if}
{#if !$userStore?.operator}
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />

View File

@@ -88,7 +88,7 @@
minHeight={400}
modules={flow?.value?.modules}
failureModule={flow?.value?.failure_module}
on:select={(e) => (stepDetail = e.detail)}
on:click={(e) => (stepDetail = e.detail)}
/>
</div>
{#if !noSide}

View File

@@ -3,8 +3,6 @@
import { workspaceStore } from '$lib/stores'
export let name: string
export let tabular = false
export let order = ['scripts', 'flows', 'apps', 'schedules', 'variables', 'resources']
$: $workspaceStore && loadUsage()
@@ -15,18 +13,10 @@
}
</script>
{#if tabular}
{#each order as key}
<td class="text-center">
{usage[key] ?? ''}
</td>
<div class="flex flex-col text-xs text-gray-600">
{#each Object.entries(usage) as [k, v]}
<div>
{k}: {v}
</div>
{/each}
{:else}
<div class="flex flex-col text-xs text-gray-600">
{#each Object.entries(usage) as [k, v]}
<div>
{k}: {v}
</div>
{/each}
</div>
{/if}
</div>

View File

@@ -234,14 +234,6 @@
name,
requestBody: { username: member_name }
})
await GranularAclService.removeGranularAcls({
workspace: $workspaceStore ?? '',
path: name,
kind: 'group_',
requestBody: {
owner: 'u/' + member_name
}
})
loadGroup()
}}>remove</button
>

View File

@@ -1,331 +0,0 @@
<script lang="ts">
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import { setInputCat as computeInputCat } from '$lib/utils'
import { Badge, Button } from './common'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import FieldHeader from './FieldHeader.svelte'
import ObjectResourceInput from './ObjectResourceInput.svelte'
import ResourcePicker from './ResourcePicker.svelte'
import type { SchemaProperty } from '$lib/common'
import autosize from 'svelte-autosize'
import Toggle from './Toggle.svelte'
import Range from './Range.svelte'
import LightweightSchemaForm from './LightweightSchemaForm.svelte'
export let label: string = ''
export let value: any
export let defaultValue: any = undefined
export let description: string = ''
export let format: string = ''
export let contentEncoding: 'base64' | 'binary' | undefined = undefined
export let type: string | undefined = undefined
export let required = false
export let pattern: undefined | string = undefined
export let valid = required ? false : true
export let maxRows = 10
export let enum_: string[] | undefined = undefined
export let itemsType:
| { type?: 'string' | 'number' | 'bytes'; contentEncoding?: 'base64' }
| undefined = undefined
export let displayHeader = true
export let properties: { [name: string]: SchemaProperty } | undefined = undefined
export let extra: Record<string, any> = {}
const dispatch = createEventDispatcher()
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
$: validateInput(pattern, value)
let error: string = ''
let el: HTMLTextAreaElement | undefined = undefined
let rawValue: string | undefined = undefined
$: {
if (rawValue) {
try {
value = JSON.parse(rawValue)
error = ''
} catch (err) {
error = err.toString()
}
}
}
$: {
error = ''
if (inputCat === 'object') {
evalValueToRaw()
validateInput(pattern, value)
}
}
export function evalValueToRaw() {
if (value) {
rawValue = JSON.stringify(value, null, 4)
}
}
function fileChanged(e: any, cb: (v: string | undefined) => void) {
let t = e.target
if (t && 'files' in t && t.files.length > 0) {
let reader = new FileReader()
reader.onload = (e: any) => {
cb(e.target.result.split('base64,')[1])
}
reader.readAsDataURL(t.files[0])
} else {
cb(undefined)
}
}
export function focus() {
el?.focus()
if (el) {
el.style.height = '5px'
el.style.height = el.scrollHeight + 50 + 'px'
}
}
function validateInput(pattern: string | undefined, v: any): void {
if (required && (v == undefined || v == null || v === '')) {
error = 'This field is required'
valid = false
} else {
if (pattern && !testRegex(pattern, v)) {
error = `Should match ${pattern}`
valid = false
} else {
error = ''
valid = true
}
}
}
function testRegex(pattern: string, value: any): boolean {
try {
const regex = new RegExp(pattern)
return regex.test(value)
} catch (err) {
return false
}
}
$: {
if (value == undefined || value == null) {
value = defaultValue
if (defaultValue === undefined || defaultValue === null) {
if (inputCat === 'string') {
value = ''
} else if (inputCat == 'enum') {
value = enum_?.[0]
} else if (inputCat == 'boolean') {
value = false
}
}
}
}
$: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)
</script>
<div class="flex flex-col w-full min-w-[250px]">
<div>
{#if displayHeader}
<FieldHeader {label} {required} {type} {contentEncoding} {format} {itemsType} />
{/if}
{#if description}
<div class="text-sm italic pb-1">
{description}
</div>
{/if}
<div class="flex space-x-1">
{#if inputCat == 'number'}
{#if extra['min'] != undefined && extra['max'] != undefined}
<div class="flex w-full gap-1">
<span>{extra['min']}</span>
<div class="grow">
<Range bind:value min={extra['min']} max={extra['max']} />
</div>
<span>{extra['max']}</span>
<span class="mx-2"><Badge large color="blue">{value}</Badge></span>
</div>
{:else}
<input
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
type="number"
class={valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}
placeholder={defaultValue ?? ''}
bind:value
min={extra['min']}
max={extra['max']}
on:input={() => dispatch('input', { value, isRaw: true })}
/>
{/if}
{:else if inputCat == 'boolean'}
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
class={valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}
bind:checked={value}
/>
{#if type == 'boolean' && value == undefined}
<span>&nbsp; Not set</span>
{/if}
{:else if inputCat == 'list'}
<div>
<div>
{#each value ?? [] as v, i}
<div class="flex flex-row max-w-md mt-1">
{#if itemsType?.type == 'number'}
<input type="number" bind:value={v} />
{:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'}
<input
type="file"
class="my-6"
on:change={(x) => fileChanged(x, (val) => (value[i] = val))}
multiple={false}
/>
{:else}
<input type="text" bind:value={v} />
{/if}
<Button
variant="border"
color="red"
size="sm"
btnClasses="mx-6"
on:click={() => {
value = value.filter((el) => el != v)
if (value.length == 0) {
value = undefined
}
}}
>
<Icon data={faMinus} />
</Button>
</div>
{/each}
</div>
<Button
variant="border"
color="blue"
size="sm"
btnClasses="mt-1"
on:click={() => {
if (value == undefined || !Array.isArray(value)) {
value = []
}
value = value.concat('')
}}
>
<Icon data={faPlus} class="mr-2" />
Add item
</Button>
<span class="ml-2">
{(value ?? []).length} item{(value ?? []).length > 1 ? 's' : ''}
</span>
</div>
{:else if inputCat == 'resource-object'}
<ObjectResourceInput {format} bind:value />
{:else if inputCat == 'object'}
{#if properties && Object.keys(properties).length > 0}
<div class="p-4 pl-8 border rounded w-full">
<LightweightSchemaForm
schema={{ properties, $schema: '', required: [], type: 'object' }}
bind:args={value}
/>
</div>
{:else}
<textarea
bind:this={el}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
use:autosize
style="max-height: {maxHeight}"
on:input={() => {
dispatch('input', { rawValue: value, isRaw: false })
}}
class="col-span-10 {valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}"
placeholder={defaultValue ? JSON.stringify(defaultValue, null, 4) : ''}
bind:value={rawValue}
/>
{/if}
{:else if inputCat == 'enum'}
<select
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
class="px-6"
bind:value
>
{#each enum_ ?? [] as e}
<option>{e}</option>
{/each}
</select>
{:else if inputCat == 'date'}
<input class="inline-block" type="datetime-local" bind:value />
{:else if inputCat == 'base64'}
<input
type="file"
class="my-6"
on:change={(x) => fileChanged(x, (val) => (value = val))}
multiple={false}
/>
{:else if inputCat == 'resource-string'}
<ResourcePicker
bind:value
resourceType={format.split('-').length > 1
? format.substring('resource-'.length)
: undefined}
/>
{:else if inputCat == 'string'}
<div class="flex flex-col w-full">
<div class="flex flex-row w-full items- justify-between">
<textarea
rows="1"
bind:this={el}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
on:blur={() => dispatch('blur')}
use:autosize
type="text"
class="col-span-10 {valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}"
placeholder={defaultValue ?? ''}
bind:value
on:input={() => {
dispatch('input', { rawValue: value, isRaw: false })
}}
/>
</div>
</div>
{/if}
<slot name="actions" />
</div>
</div>
</div>

View File

@@ -1,35 +0,0 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import LightweightArgInput from './LightweightArgInput.svelte'
export let schema: Schema
export let args: Record<string, any> | undefined = undefined
$: if (args === undefined) {
args = {}
}
</script>
<div class="w-full">
{#each Object.keys(schema.properties ?? {}) as argName, i (argName)}
<div>
{#if typeof args == 'object' && schema?.properties[argName] && args}
<LightweightArgInput
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}
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}
extra={schema.properties[argName]}
/>
{/if}
</div>
{/each}
</div>

View File

@@ -60,7 +60,6 @@
if (testJob && !testJob.canceled && testJob.type == 'CompletedJob' && `result` in testJob) {
if ($flowStateStore[mod.id]?.previewResult) {
$flowStateStore[mod.id].previewResult = testJob.result
$flowStateStore = $flowStateStore
}
}
}

View File

@@ -11,7 +11,7 @@
<span class="flex items-center space-x-2 mb-2">
<h1>{title}</h1>
{#if tooltip}
<Tooltip scale={0.9}>{tooltip}</Tooltip>
<Tooltip>{tooltip}</Tooltip>
{/if}
</span>
{:else}

View File

@@ -23,7 +23,6 @@
import FolderEditor from './FolderEditor.svelte'
import GroupEditor from './GroupEditor.svelte'
import { random_adj } from './random_positive_adjetive'
import Badge from './common/badge/Badge.svelte'
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
let meta: Meta | undefined = undefined
@@ -200,10 +199,6 @@
}
function initPath() {
if (path != undefined && path != '') {
meta = pathToMeta(path)
return
}
if (initialPath == undefined || initialPath == '') {
reset()
} else {
@@ -311,7 +306,7 @@
{#if meta != undefined}
<div class="flex gap-4 shrink">
<label class="block">
<span class="text-gray-700 text-sm whitespace-nowrap">Owner</span>
<span class="text-gray-700 text-sm whitespace-nowrap">&nbsp;</span>
<ToggleButtonGroup
class="mt-0.5"
@@ -414,26 +409,8 @@
{/if}
</div>
<div class="flex flex-col w-full mt-4">
<div class="flex justify-start w-full">
<Badge
color="gray"
class="center-center !bg-gray-300 !text-gray-600 !w-[70px] !h-[24px] rounded-r-none"
>
Full path
</Badge>
<input
type="text"
readonly
value={path}
size={path?.length || 50}
class="font-mono !text-xs max-w-[calc(100%-70px)] !w-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
}}
/>
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
</div>
<div class="flex-row flex justify-between w-full">
<div><span class="font-mono text-sm break-all">{path}</span></div>
<div class="text-red-600 text-2xs">{error}</div>
</div>
</div>

View File

@@ -88,7 +88,7 @@
}}
items={collection}
class="text-clip grow min-w-0"
placeholder="{resourceType ?? 'any'} resource"
placeholder="{resourceType} resource"
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
/>

View File

@@ -45,7 +45,7 @@
label: 'Failed',
data:
failed?.map((job) => ({
x: job.started_at as any,
x: job.created_at as any,
y: job.duration_ms,
id: job.id,
path: job.script_path
@@ -57,7 +57,7 @@
label: 'Successful',
data:
success?.map((job) => ({
x: job.started_at as any,
x: job.created_at as any,
y: job.duration_ms,
id: job.id,
path: job.script_path
@@ -71,10 +71,7 @@
enabled: true,
modifierKey: 'ctrl' as 'ctrl',
onPanComplete: ({ chart }) => {
dispatch('zoom', {
min: addSeconds(new Date(chart.scales.x.min), -1),
max: addSeconds(new Date(chart.scales.x.max), 1)
})
dispatch('zoom', { min: new Date(chart.scales.x.min), max: new Date(chart.scales.x.max) })
}
},
zoom: {
@@ -83,10 +80,7 @@
},
mode: 'x' as 'x',
onZoom: ({ chart }) => {
dispatch('zoom', {
min: addSeconds(new Date(chart.scales.x.min), -1),
max: addSeconds(new Date(chart.scales.x.max), 1)
})
dispatch('zoom', { min: new Date(chart.scales.x.min), max: new Date(chart.scales.x.max) })
}
}
}
@@ -94,22 +88,6 @@
function getPath(x: any): string {
return x.path
}
$: minTime = getMinTime(jobs)
function addSeconds(date: Date, seconds: number): Date {
date.setTime(date.getTime() + seconds * 1000)
return date
}
function getMinTime(jobs: CompletedJob[] | undefined): Date {
return addSeconds(new Date(jobs?.[jobs?.length - 1]?.started_at ?? new Date().toString()), -15)
}
$: maxTime = getMaxTime(jobs)
function getMaxTime(jobs: CompletedJob[] | undefined): Date {
return addSeconds(new Date(jobs?.[0]?.started_at ?? new Date().toString()), 15)
}
</script>
<Scatter
@@ -137,8 +115,7 @@
display: false
},
type: 'time',
min: minTime,
max: maxTime
min: jobs?.[jobs?.length - 1]?.created_at ?? new Date().toString()
},
y: {
grid: {

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { VariableService } from '$lib/gen'
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'
@@ -10,7 +10,7 @@
import VariableEditor from './VariableEditor.svelte'
export let schema: Schema
export let args: Record<string, any> = {}
export let args: Record<string, InputTransform | any> = {}
export let disabledArgs: string[] = []
export let disabled = false

View File

@@ -1,23 +1,23 @@
<script lang="ts">
import { Script, ScriptService } from '$lib/gen'
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { inferArgs } from '$lib/infer'
import { initialCode } from '$lib/script_helpers'
import { initialCode, isInitialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import { emptySchema, encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
import Path from './Path.svelte'
import RadioButton from './RadioButton.svelte'
import ScriptEditor from './ScriptEditor.svelte'
import ScriptSchema from './ScriptSchema.svelte'
import CenteredPage from './CenteredPage.svelte'
import { dirtyStore } from './common/confirmationModal/dirtyStore'
import { Button, ButtonPopup, ButtonPopupItem, Kbd } from './common'
import { faPen, faSave } from '@fortawesome/free-solid-svg-icons'
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
import type { SupportedLanguage } from '$lib/common'
import Tooltip from './Tooltip.svelte'
import SettingSection from './SettingSection.svelte'
export let script: Script
export let initialPath: string = ''
@@ -31,34 +31,14 @@
['Go', Script.language.GO],
['Bash', Script.language.BASH]
]
const scriptKindOptions: { value: Script.kind; title: string; desc?: string }[] = [
{
value: Script.kind.SCRIPT,
title: 'Action'
},
{
value: Script.kind.TRIGGER,
title: 'Trigger',
desc: 'First module of flows to trigger them based on external changes. These kind of scripts are usually running on a schedule to periodically look for changes.'
},
{
value: Script.kind.APPROVAL,
title: 'Approval',
desc: 'Send notifications externally to ask for approval to continue a flow.'
},
{
value: Script.kind.FAILURE,
title: 'Error Handler',
desc: 'Handle errors in flows after all retry attempts have been exhausted.'
}
]
let viewScriptKind = script.kind !== Script.kind.SCRIPT
let pathError = ''
let summaryC: HTMLInputElement | undefined = undefined
let pathC: Path | undefined = undefined
let loadingSave = false
$: setQueryWithoutLoad($page.url, [{ key: 'state', value: encodeState(script) }])
$: setQueryWithoutLoad($page.url, 'state', encodeState(script))
$: step = Number($page.url.searchParams.get('step')) || 1
if (script.content == '') {
@@ -73,6 +53,7 @@
script.content = initialCode(language, kind, template)
}
let loadingSave = false
async function editScript(leave: boolean): Promise<void> {
loadingSave = true
try {
@@ -230,111 +211,132 @@
<!-- metadata -->
{#if step === 1}
<CenteredPage>
<SettingSection title="Path" element="h2">
<Path
bind:this={pathC}
bind:error={pathError}
bind:path={script.path}
{initialPath}
on:enter={() => changeStep(2)}
namePlaceholder="script"
kind="script"
/>
</SettingSection>
<SettingSection title="Summary" element="h2">
<input
type="text"
bind:this={summaryC}
bind:value={script.summary}
placeholder="Short summary to be displayed when listed"
/>
</SettingSection>
<SettingSection title="Language" element="h2">
{#if lockedLanguage}
<div class="text-sm text-gray-600 italic mb-2">
As a forked script, the language '{script.language}' cannot be modified.
</div>
{/if}
<div class="flex flex-row gap-2 flex-wrap">
{#each langs as [label, lang]}
{@const isPicked = script.language == lang && template == 'script'}
<Button
size="sm"
variant="border"
color={isPicked ? 'blue' : 'dark'}
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = lang
template = 'script'
initContent(lang, script.kind, template)
}}
disabled={lockedLanguage}
>
<LanguageIcon {lang} />
<span class="ml-2 py-2">{label}</span>
</Button>
{/each}
<h2 class="border-b pb-1 mt-8 mb-2">Path</h2>
<Path
bind:this={pathC}
bind:error={pathError}
bind:path={script.path}
{initialPath}
on:enter={() => changeStep(2)}
namePlaceholder="script"
kind="script"
/>
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
<input
type="text"
bind:this={summaryC}
bind:value={script.summary}
placeholder="Short summary to be displayed when listed"
/>
<h2 class="border-b pb-1 mt-12 mb-6">Language</h2>
{#if lockedLanguage}
<div class="text-sm text-gray-600 italic mb-2">
As a forked script, the language '{script.language}' cannot be modified.
</div>
{/if}
<div class="flex flex-row gap-2 flex-wrap">
{#each langs as [label, lang]}
{@const isPicked = script.language == lang && template == 'script'}
<Button
size="sm"
variant="border"
color={template == 'pgsql' ? 'blue' : 'dark'}
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
color={isPicked ? 'blue' : 'dark'}
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = lang
template = 'script'
initContent(lang, script.kind, template)
}}
disabled={lockedLanguage}
on:click={() => {
script.language = Script.language.DENO
template = 'pgsql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="pgsql" /><span class="ml-2 py-2">PostgreSQL</span>
<LanguageIcon {lang} /><span class="ml-2 py-4">{label}</span>
</Button>
<!-- <Button
size="sm"
variant="border"
color={template == 'mysql' ? 'blue' : 'dark'}
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = Script.language.DENO
template = 'mysql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
</Button> -->
</div>
</SettingSection>
<SettingSection
title="Script kind"
element="h3"
tooltip="Tag this script as having a specific purpose inside flows. If it won't be used in flows,
you don't have to worry about this."
accordion
>
<div class="flex flex-wrap gap-2">
{#each scriptKindOptions as { value, title, desc }}
{@const isPicked = script.kind === value}
<Button
size="sm"
variant="border"
color={isPicked ? 'blue' : 'dark'}
btnClasses="font-medium {isPicked ? '!bg-blue-50/75' : ''}"
on:click={() => {
{/each}
<Button
size="sm"
variant="border"
color={template == 'pgsql' ? 'blue' : 'dark'}
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
disabled={lockedLanguage}
on:click={() => {
script.language = Script.language.DENO
template = 'pgsql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
</Button>
<!-- <Button
size="sm"
variant="border"
color={template == 'mysql' ? 'blue' : 'dark'}
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
on:click={() => {
script.language = Script.language.DENO
template = 'mysql'
initContent(script.language, script.kind, template)
}}
>
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
</Button> -->
</div>
<div class="mt-16 mb-4">
<Button
color="light"
size="sm"
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
on:click={() => (viewScriptKind = !viewScriptKind)}
>
Tag this script as having a specific purpose inside flows
</Button>
</div>
{#if viewScriptKind}
<div class="max-w-lg">
<RadioButton
label="Script Type"
options={[
['Action', Script.kind.SCRIPT],
[
{
title: 'Trigger',
desc: `First module of flows to trigger them based on watching changes external periodically using an internal state`
},
Script.kind.TRIGGER
],
[
{
title: 'Approval',
desc: `Send notification externally to ask for approval to continue a flow`
},
Script.kind.APPROVAL
],
[
{
title: 'Error Handler',
desc: `Handle errors for flows after all retries attempts have been exhausted`
},
Script.kind.FAILURE
]
// ['Command Handler', Script.kind.COMMAND]
]}
on:change={(e) => {
if (isInitialCode(script.content)) {
template = 'script'
script.kind = value
initContent(script.language, value, template)
}}
disabled={lockedLanguage}
>
{title}
{#if desc}
<Tooltip class="mb-0.5 ml-1">
{desc}
</Tooltip>
{/if}
</Button>
{/each}
initContent(script.language, e.detail, template)
}
}}
bind:value={script.kind}
/>
</div>
</SettingSection>
{/if}
<!-- <div class="ml-3">
<Toggle
bind:checked={script.is_template}
options={{ right: 'Save as a workspace template' }}
/>
</div> -->
</CenteredPage>
{:else if step === 2}
<ScriptEditor
@@ -344,6 +346,7 @@
lang={script.language}
{initialArgs}
{kind}
tour
/>
{:else if step === 3}
<CenteredPage>

View File

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

View File

@@ -1,47 +0,0 @@
<script lang="ts">
import { ChevronDown } from 'lucide-svelte'
import { slide } from 'svelte/transition'
import type { IntRange } from '../common'
import Tooltip from './Tooltip.svelte'
export let title: string
export let tooltip: string = ''
export let element: `h${IntRange<1, 6>}` = 'h2'
export let accordion = false
let showContent = !accordion
</script>
<div class="border-b [&:has(button:hover)]:border-gray-400 duration-200 pb-1 mt-8 mb-2">
{#if accordion}
<button
class="flex w-full justify-start items-center"
on:click={() => (showContent = !showContent)}
>
<span class="rounded-full hover:bg-gray-100 focus:bg-gray-100 p-1 mr-1">
<ChevronDown size={22} class="rotate-0 duration-300 {showContent ? '!-rotate-180' : ''}" />
</span>
<svelte:element this={element}>
{title}
{#if tooltip}
<Tooltip scale={0.9} class="mb-0.5">
{tooltip}
</Tooltip>
{/if}
</svelte:element>
</button>
{:else}
<svelte:element this={element}>
{title}
{#if tooltip}
<Tooltip scale={0.9} class="mb-0.5">
{tooltip}
</Tooltip>
{/if}
</svelte:element>
{/if}
</div>
{#if showContent}
<div transition:slide={{ duration: 300 }}>
<slot />
</div>
{/if}

View File

@@ -99,7 +99,7 @@
}
}
export function format() {
function format() {
if (editor) {
code = getCode()
editor.getAction('editor.action.formatDocument').run()

View File

@@ -2,18 +2,13 @@
import { ChevronDown, ChevronUp } from 'lucide-svelte'
import { slide } from 'svelte/transition'
import Button from './common/button/Button.svelte'
import Tooltip from './Tooltip.svelte'
export let text: string
export let tooltip: string | undefined = undefined
export let view = false
</script>
<Button color="light" on:click={() => (view = !view)} variant="border"
>{text}
{#if tooltip}
<Tooltip wrapperClass="mx-1">{tooltip}</Tooltip>
{/if}
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
>
{#if view}

View File

@@ -577,10 +577,6 @@
})
}
export function focus() {
editor?.focus()
}
onMount(() => {
if (browser) {
loadMonaco()

View File

@@ -16,7 +16,7 @@
const dispatch = createEventDispatcher()
</script>
<span class="{$$props.class} z-auto">
<span class="{$$props.class ?? ''} z-auto">
<label
for={id}
class="inline-flex items-center mt-2 duration-200 {disabled

View File

@@ -18,7 +18,5 @@
data={faInfoCircle}
{scale}
/>
<svelte:fragment slot="text">
<slot />
</svelte:fragment>
<span slot="text"> <slot /> </span>
</Popover>

View File

@@ -57,8 +57,7 @@
decryptSecret: false
})
can_write =
getV.workspace_id == $workspaceStore &&
canWrite(edit_path, getV.extra_perms ?? {}, $userStore)
getV.workspace_id == $workspaceStore && canWrite(path, getV.extra_perms ?? {}, $userStore)
variable = {
value: getV.value ?? '',

View File

@@ -9,7 +9,9 @@
...new Set(
workers
.filter((worker) => {
return worker.ip != 'unretrievable IP' && worker.last_ping && worker.last_ping < 300
const date = new Date().getTime() - 300 * 60
const ping_at = new Date(worker.ping_at).getTime()
return worker.ip != 'unretrievable IP' && ping_at > date
})
.map((worker) => worker.ip)
)

View File

@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -11,12 +11,10 @@
import { loadIcon } from '../icon'
import { twMerge } from 'tailwind-merge'
import { goto } from '$app/navigation'
import { initOutput } from '../../editor/appUtils'
import { configurationKeys } from '../../editor/component'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -25,79 +23,57 @@
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = true
export let controls: { left: () => boolean; right: () => boolean | string } | undefined =
undefined
export const staticOutputs: string[] = ['loading', 'result']
const { worldStore, app, componentControl } = getContext<AppViewerContext>('AppViewerContext')
if (controls) {
$componentControl[id] = controls
}
const { runnableComponents, worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string
let color: ButtonType.Color
let size: ButtonType.Size
let runnableComponent: RunnableComponent
// let labelValue: string
// let color: ButtonType.Color
// let size: ButtonType.Size
// let disabled: boolean | undefined = undefined
// let fillContainer: boolean | undefined = undefined
// let gotoUrl: string | undefined = undefined
// let gotoNewTab: boolean | undefined = undefined
let disabled: boolean | undefined = undefined
let fillContainer: boolean | undefined = undefined
let gotoUrl: string | undefined = undefined
let gotoNewTab: boolean | undefined = undefined
let isLoading: boolean = false
let ownClick: boolean = false
let triggerOnAppLoad = false
// let beforeIcon: undefined | string = undefined
// let afterIcon: undefined | string = undefined
let resolvedConfig = {} as {
beforeIcon?: string
afterIcon?: string
label?: string
color?: ButtonType.Color
size?: ButtonType.Size
disabled?: boolean
fillContainer?: boolean
gotoUrl?: string
gotoNewTab?: boolean
triggerOnAppLoad?: boolean
}
$: initializing = resolvedConfig?.label == undefined
let beforeIcon: undefined | string = undefined
let afterIcon: undefined | string = undefined
let beforeIconComponent: any
let afterIconComponent: any
$: resolvedConfig.beforeIcon && handleBeforeIcon()
$: resolvedConfig.afterIcon && handleAfterIcon()
$: beforeIcon && handleBeforeIcon()
$: afterIcon && handleAfterIcon()
async function handleBeforeIcon() {
if (resolvedConfig.beforeIcon) {
beforeIconComponent = await loadIcon(resolvedConfig.beforeIcon)
if (beforeIcon) {
beforeIconComponent = await loadIcon(beforeIcon)
}
}
async function handleAfterIcon() {
if (resolvedConfig.afterIcon) {
afterIconComponent = await loadIcon(resolvedConfig.afterIcon)
if (afterIcon) {
afterIconComponent = await loadIcon(afterIcon)
}
}
let outputs = initOutput($worldStore, id, {
result: undefined,
loading: false
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<Array<any>>
loading: Output<boolean>
}
$: resolvedConfig.triggerOnAppLoad && runnableComponent?.runComponent()
$: triggerOnAppLoad && runnableComponent?.runComponent()
$: if (outputs?.loading != undefined) {
outputs.loading.set(false, true)
}
$: outputs?.loading.subscribe({
id: 'loading-' + id,
next: (value) => {
isLoading = value
if (ownClick && !value) {
@@ -123,32 +99,49 @@
ownClick = true
if (!runnableComponent) {
if (resolvedConfig.gotoUrl) {
if (resolvedConfig.gotoNewTab) {
window.open(resolvedConfig.gotoUrl, '_blank')
if (gotoUrl) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(resolvedConfig.gotoUrl)
goto(gotoUrl)
}
}
} else {
await runnableComponent?.runComponent()
}
if (recomputeIds) {
await Promise.all(recomputeIds.map((id) => $runnableComponents?.[id]?.()))
}
}
</script>
{#each configurationKeys['buttoncomponent'] as key (key)}
<InputValue {key} {id} input={configuration[key]} bind:value={resolvedConfig[key]} />
{/each}
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<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} />
<InputValue {id} input={configuration.afterIcon} bind:value={afterIcon} />
<InputValue {id} input={configuration.triggerOnAppLoad} bind:value={triggerOnAppLoad} />
<InputValue
{id}
input={configuration.disabled}
bind:value={disabled}
bind:error={errors.disabled}
/>
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
<RunnableWrapper
{recomputeIds}
flexWrap
bind:runnableComponent
{componentInput}
{id}
{extraQueryParams}
autoRefresh={false}
goto={resolvedConfig.gotoUrl}
gotoNewTab={resolvedConfig.gotoNewTab}
goto={gotoUrl}
{gotoNewTab}
{render}
>
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
@@ -159,25 +152,25 @@
btnClasses={twMerge(
$app.css?.['buttoncomponent']?.['button']?.class,
customCss?.button.class,
resolvedConfig.fillContainer ? 'w-full h-full' : ''
fillContainer ? 'w-full h-full' : ''
)}
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
disabled={resolvedConfig.disabled}
{disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={handleClick}
size={resolvedConfig.size}
color={resolvedConfig.color}
{size}
{color}
{loading}
>
<span class="truncate inline-flex gap-2 items-center">
{#if resolvedConfig.beforeIcon && beforeIconComponent}
{#if beforeIcon && beforeIconComponent}
<svelte:component this={beforeIconComponent} size={14} />
{/if}
<div>{resolvedConfig.label}</div>
{#if resolvedConfig.afterIcon && afterIconComponent}
<div>{labelValue}</div>
{#if afterIcon && afterIconComponent}
<svelte:component this={afterIconComponent} size={14} />
{/if}
</span>

View File

@@ -5,7 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -14,7 +14,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -23,7 +23,8 @@
export const staticOutputs: string[] = ['loading', 'result']
const { app, worldStore, stateId } = getContext<AppViewerContext>('AppViewerContext')
const { app, runnableComponents, worldStore, stateId } =
getContext<AppViewerContext>('AppViewerContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -47,7 +48,6 @@
}
$: outputs?.loading.subscribe({
id: 'loading-' + id,
next: (value) => {
isLoading = value
}
@@ -62,7 +62,6 @@
<InputValue {id} input={configuration.size} bind:value={size} />
<RunnableWrapper
{recomputeIds}
{render}
bind:runnableComponent
{componentInput}
@@ -104,6 +103,12 @@
}}
on:click={() => {
runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
}}
{size}
{color}

View File

@@ -5,7 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -16,7 +16,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -26,7 +26,7 @@
export const staticOutputs: string[] = ['loading', 'result']
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
const { app, runnableComponents, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -57,7 +57,6 @@
}
$: outputs?.loading.subscribe({
id: 'loading-' + id,
next: (value) => {
isLoading = value
if (ownClick && !value) {
@@ -95,7 +94,6 @@
}}
>
<RunnableWrapper
{recomputeIds}
{render}
bind:runnableComponent
{componentInput}
@@ -130,6 +128,12 @@
on:click={async () => {
await runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
open = false
}}
size="xs"

View File

@@ -13,31 +13,21 @@
} from 'chart.js'
import type { ChartOptions } from 'chart.js'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput, RichAppInput } from '../../inputType'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -100,7 +90,7 @@
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.line} bind:value={lineChart} />
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
{#if lineChart}

View File

@@ -2,7 +2,6 @@
import DisplayResult from '$lib/components/DisplayResult.svelte'
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import {
IS_APP_PUBLIC_CONTEXT_KEY,
@@ -18,16 +17,13 @@
export let render: boolean
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: any = undefined
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<div
class={twMerge(
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
@@ -12,12 +11,8 @@
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['result', 'loading']
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: string | undefined = undefined
let h: number | undefined = undefined
@@ -34,7 +29,15 @@
bind:clientHeight={h}
bind:clientWidth={w}
>
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper
{render}
autoRefresh
flexWrap
{componentInput}
{id}
bind:initializing
bind:result
>
{#key result}
<iframe
frameborder="0"

View File

@@ -1,11 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { AlignWrapper, InputValue } from '../helpers'
import { loadIcon } from '../icon'
@@ -13,7 +9,8 @@
export let id: string
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = []
export let customCss: ComponentCustomCSS<'container' | 'icon'> | undefined = undefined
export let render: boolean

View File

@@ -2,14 +2,16 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
type FitOption = (typeof staticValues)['objectFitOptions'][number]
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['loading']
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
export let render: boolean

View File

@@ -1,12 +1,8 @@
<script lang="ts">
import { getContext } from 'svelte'
import { getContext, onMount } from 'svelte'
import { concatCustomCss } from '../../utils'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { InputValue } from '../helpers'
import { twMerge } from 'tailwind-merge'
import { Map, View, Feature } from 'ol'
@@ -15,7 +11,8 @@
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer'
import { Point } from 'ol/geom'
import { defaults as defaultControls } from 'ol/control'
import { findGridItem, initOutput } from '../../editor/appUtils'
import { findGridItem } from '../../editor/appUtils'
import type { Output } from '../../rx'
interface Marker {
lon: number
@@ -32,19 +29,20 @@
} as const
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['mapRegion']
export let customCss: ComponentCustomCSS<'map'> | undefined = undefined
export let render: boolean
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
getContext<AppViewerContext>('AppViewerContext')
let outputs = initOutput($worldStore, id, {
mapRegion: {
topLeft: { lat: 0, lon: 0 },
bottomRight: { lat: 0, lon: 0 }
}
})
$: outputs = $worldStore?.outputsById[id] as {
mapRegion: Output<{
topLeft: { lat: number; lon: number }
bottomRight: { lat: number; lon: number }
}>
}
let map: Map
let mapElement: HTMLDivElement
@@ -128,7 +126,7 @@
createMarkerLayers()?.forEach((l) => map.addLayer(l))
}
$: if (!map && mapElement) {
onMount(() => {
useGeographic()
map = new Map({
target: mapElement,
@@ -147,7 +145,7 @@
})
})
updateRegionOutput()
}
})
$: css = concatCustomCss($app.css?.mapcomponent, customCss)
@@ -175,7 +173,6 @@
updateRegionOutput()
if (z) {
//@ts-ignore
gridItem.data.configuration.zoom.value = z
}
@@ -185,9 +182,7 @@
}
if (gridItem) {
//@ts-ignore
gridItem.data.configuration.longitude.value = center[0]
//@ts-ignore
gridItem.data.configuration.latitude.value = center[1]
}
}

View File

@@ -3,27 +3,23 @@
import { twMerge } from 'tailwind-merge'
import { getDocument, type PDFDocumentProxy, type PDFPageProxy } from 'pdfjs-dist'
import 'pdfjs-dist/build/pdf.worker.entry'
import type { AppViewerContext, ComponentCustomCSS, RichConfiguration, RichConfigurations } from '../../types'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { throttle } from '../../../../utils'
import { Button } from '../../../common'
import { Download, Loader2, MoveHorizontal, ZoomIn, ZoomOut } from 'lucide-svelte'
import { fade } from 'svelte/transition'
import { findGridItem, initOutput } from '../../editor/appUtils'
import { findGridItem } from '../../editor/appUtils'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['loading']
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, mode, worldStore, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
const { app, mode, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let source: string | ArrayBuffer | undefined = undefined
let wrapper: HTMLDivElement | undefined = undefined
@@ -154,9 +150,7 @@
function syncZoomValue() {
const gridItem = findGridItem($app, id)
//@ts-ignore
if (gridItem && gridItem.data.configuration.zoom.value !== zoom) {
//@ts-ignore
gridItem.data.configuration.zoom.value = zoom
}

View File

@@ -14,29 +14,19 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -83,7 +73,7 @@
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
{#if doughnut}

View File

@@ -19,17 +19,11 @@
import type { ChartOptions, ChartData } from 'chart.js'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -37,12 +31,8 @@
let zoomable = false
let pannable = false
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -89,7 +79,7 @@
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
<Scatter {data} {options} />

View File

@@ -8,39 +8,27 @@
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { twMerge } from 'tailwind-merge'
import type {
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import Tooltip from '$lib/components/Tooltip.svelte'
import ResizeWrapper from '../helpers/ResizeWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let render: boolean
const { app, worldStore, mode } = getContext<AppViewerContext>('AppViewerContext')
export const staticOutputs: string[] = ['result', 'loading']
const { ontextfocus } = getContext<AppEditorContext>('AppEditorContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: string | undefined = undefined
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
let copyButton: boolean
let tooltip: string = ''
let fitContent = false
function getComponent() {
switch (style) {
@@ -78,27 +66,17 @@
<InputValue {id} input={configuration.style} bind:value={style} />
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
<InputValue {id} input={configuration.tooltip} bind:value={tooltip} />
<InputValue {id} input={configuration.fitContent} bind:value={fitContent} />
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
<div class="h-full w-full overflow-hidden">
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<ResizeWrapper {id} shouldWrap={fitContent}>
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
No text
</div>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex flex-wrap gap-2 pb-0.5 {$mode === 'dnd' && componentInput?.type == 'template'
? 'cursor-text'
: ''}"
on:click={() => {
if ($mode === 'dnd' && componentInput?.type == 'template') {
$ontextfocus?.()
}
}}
>
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
<svelte:element
this={component}
class={twMerge(
@@ -110,26 +88,17 @@
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
>
{String(result)}
{#if tooltip != ''}
<Tooltip>{tooltip}</Tooltip>
{/if}
{#if copyButton && result}
<Popover notClickable>
<Button
variant="border"
size="xs"
color="dark"
btnClasses="!p-1"
on:click={() => copyToClipboard(result)}
>
<Clipboard size={14} strokeWidth={2} />
</Button>
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
</Popover>
{/if}
</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>
</div>
</ResizeWrapper>
</RunnableWrapper>

View File

@@ -20,29 +20,19 @@
import { Scatter } from 'svelte-chartjs'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
import { concatCustomCss } from '../../utils'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppViewerContext>('AppViewerContext')
let logarithmicScale = false
let zoomable = false
@@ -104,7 +94,7 @@
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
<Scatter {data} {options} />

View File

@@ -1,8 +1,6 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
@@ -11,12 +9,7 @@
export let initializing: boolean | undefined = undefined
export let render: boolean
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['result', 'loading']
let result: object | undefined = undefined
let divEl: HTMLDivElement | null = null
@@ -41,13 +34,13 @@
Plotly.newPlot(
divEl,
[result],
{ width: w, height: h, margin: { l: 50, r: 40, b: 40, t: 40, pad: 4 } },
{ width: w, height: h, margin: { l: 40, r: 40, b: 40, t: 40, pad: 4 } },
{ responsive: true, displayModeBar: false }
)
</script>
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -1,27 +1,20 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import { onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, RichConfiguration, RichConfigurations } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
let result: object | undefined = undefined
let divEl: HTMLDivElement | null = null
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
let vegaEmbed
onMount(async () => {
if (divEl) {
@@ -60,7 +53,7 @@
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -5,32 +5,26 @@
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppViewerContext, RichConfiguration, RichConfigurations } from '../../../types'
import type { AppViewerContext } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
import { isObject } from '$lib/utils'
import Alert from '$lib/components/common/alert/Alert.svelte'
import { initOutput } from '$lib/components/apps/editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
let result: Record<number, any>[] | undefined = undefined
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'selectedRowIndex']
let result: Record<string, any>[] | undefined = undefined
const { worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let outputs = initOutput($worldStore, id, {
selectedRowIndex: 0,
selectedRow: {},
result: [] as Record<number, any>[],
loading: false
})
let selectedRowIndex = -1
function toggleRow(row: Record<string, any>, rowIndex: number) {
@@ -54,7 +48,13 @@
outputs &&
toggleRow({ original: result[0] }, 0)
$: outputs?.result?.set(result ?? [])
$: outputs = $worldStore?.outputsById[id] as {
selectedRowIndex: Output<number>
selectedRow: Output<any>
result: Output<any>
}
$: outputs?.result?.set(result)
let clientHeight
let clientWidth
@@ -81,7 +81,7 @@
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
<InputValue {id} input={configuration.pageSize} bind:value={pageSize} />
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} 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"
@@ -109,10 +109,7 @@
</div>
{:else if result != undefined}
<Alert title="Parsing issues" type="error" size="xs">
The result should be an array of objects, received:
<pre class="overflow-auto">
{JSON.stringify(result)}
</pre>
The result should be an array of objects
</Alert>
{/if}
</RunnableWrapper>

View File

@@ -1,12 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type {
AppViewerContext,
BaseAppComponent,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../../types'
import type { Output } from '../../../rx'
import type { AppViewerContext, BaseAppComponent, ComponentCustomCSS } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -21,11 +16,10 @@
import type { ButtonComponent } from '../../../editor/component'
import { concatCustomCss } from '../../../utils'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '$lib/components/apps/editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
export let initializing: boolean | undefined = undefined
export let customCss:
@@ -33,6 +27,14 @@
| undefined = undefined
export let render: boolean
export const staticOutputs: string[] = [
'selectedRow',
'loading',
'result',
'search',
'selectedRowIndex'
]
type T = Record<string, any>
let result: Record<string, any>[] | undefined = undefined
@@ -55,8 +57,11 @@
let table = createSvelteTable(options)
const { app, worldStore, componentControl, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
const {
app,
worldStore,
staticOutputs: staticOutputsStore
} = getContext<AppViewerContext>('AppViewerContext')
let selectedRowIndex = -1
@@ -126,13 +131,11 @@
$: filteredResult && setOptions(filteredResult)
$: search === 'By Component' && (filteredResult = searchInResult(result ?? [], searchValue))
$: (search === 'By Runnable' || search === 'Disabled') && (filteredResult = result ?? [])
let outputs = initOutput($worldStore, id, {
selectedRowIndex: 0,
selectedRow: undefined,
loading: false,
result: [],
search: ''
})
$: outputs = $worldStore?.outputsById[id] as {
selectedRowIndex: Output<number>
selectedRow: Output<any>
search: Output<string>
}
function rerender() {
table = createSvelteTable(options)
@@ -144,27 +147,11 @@
$: result && rerender()
$: css = concatCustomCss($app.css?.tablecomponent, customCss)
$componentControl[id] = {
right: (skipActions: boolean | undefined) => {
if (skipActions) {
return false
}
const hasActions = actionButtons.length >= 1
if (hasActions) {
$selectedComponent = actionButtons[0].id
return true
}
return false
}
}
</script>
<InputValue {id} input={configuration.search} bind:value={search} />
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
{#if Array.isArray(result) && result.every(isObject)}
<div
class={twMerge(
@@ -242,7 +229,6 @@
{#if context}
{@const component = renderCell(cell.column.columnDef.cell, context)}
<td
on:keydown={() => toggleRow(row, rowIndex)}
on:click={() => toggleRow(row, rowIndex)}
class="p-4 whitespace-pre-wrap truncate text-xs text-gray-900"
>
@@ -257,74 +243,33 @@
{/each}
{#if actionButtons.length > 0}
<td
class="p-2"
on:keypress={() => toggleRow(row, rowIndex)}
on:click={() => toggleRow(row, rowIndex)}
>
<div class="center-center h-full w-full flex-wrap gap-1 ">
<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)}
<div
class={actionButton.id === $selectedComponent
? 'outline outline-indigo-500 outline-1 outline-offset-1 relative '
: ''}
>
{#if actionButton.id === $selectedComponent}
<span
title={`Id: ${actionButton.id}`}
class={classNames(
'px-2 text-2xs font-bold w-fit absolute shadow -top-5 -left-[2px] border z-50 rounded-sm',
'bg-indigo-500/90 border-indigo-600 text-white'
)}
>
{actionButton.id}
</span>
{/if}
{#if rowIndex == 0}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
controls={{
left: () => {
if (actionIndex === 0) {
$selectedComponent = id
return true
} else if (actionIndex > 0) {
$selectedComponent = actionButtons[actionIndex - 1].id
return true
}
return false
},
right: () => {
if (actionIndex === actionButtons.length - 1) {
return id
} else if (actionIndex < actionButtons.length - 1) {
$selectedComponent = actionButtons[actionIndex + 1].id
return true
}
return false
}
}}
/>
{:else}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
/>
{/if}
</div>
{#if rowIndex == 0}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{:else}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
/>
{/if}
{/each}
</div>
</td>
@@ -345,10 +290,7 @@
</div>
{:else if result != undefined}
<Alert title="Parsing issues" type="error" size="xs">
The result should be an array of objects. Received:
<pre class="overflow-auto">
{JSON.stringify(result)}
</pre>
The result should be an array of objects
</Alert>
{/if}
</RunnableWrapper>

View File

@@ -1,36 +1,27 @@
<script lang="ts">
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '../../inputType'
import type { AppViewerContext, InlineScript } from '../../types'
import type { InlineScript } from '../../types'
import RunnableComponent from './RunnableComponent.svelte'
export let id: string
export let name: string
export let inlineScript: InlineScript | undefined
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let autoRefresh: boolean = false
let result: any = undefined
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
loading: false
})
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableComponent
render={false}
{id}
{fields}
{autoRefresh}
bind:result
runnable={{
name,
@@ -38,7 +29,6 @@
type: 'runnableByName'
}}
wrapperClass="hidden"
recomputable
>
<slot />
</RunnableComponent>

View File

@@ -1,33 +1,20 @@
<script lang="ts">
import { isCodeInjection } from '$lib/components/flows/utils'
import { deepEqual } from 'fast-equals'
import { createEventDispatcher, getContext } from 'svelte'
import type { AppInput, EvalAppInput, RichAppInput, UploadAppInput } from '../../inputType'
import type { AppViewerContext, RichConfiguration } from '../../types'
import { getContext } from 'svelte'
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import { accessPropertyByPath } from '../../utils'
import { computeGlobalContext, eval_like } from './eval'
type T = string | number | boolean | Record<string | number, any> | undefined
export let input: AppInput | RichConfiguration
export let input: AppInput
export let value: T
export let id: string | undefined = undefined
export let error: string = ''
export let extraContext: Record<string, any> = {}
export let key: string = ''
$: console.log(value)
const { componentControl } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
if (input == undefined) {
dispatch('done')
}
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
$: console.log(input)
$: if (input && !deepEqual(input, lastInput)) {
lastInput = JSON.parse(JSON.stringify(input))
// Needed because of file uploads
@@ -36,13 +23,13 @@
}
}
const { worldStore, state, mode } = getContext<AppViewerContext>('AppViewerContext')
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
$: stateId = $worldStore?.stateId
$: state = $worldStore?.state
let timeout: NodeJS.Timeout | undefined = undefined
const debounce_ms = 50
function debounce(cb: () => Promise<void>) {
function debounce(cb: () => void) {
if (timeout) {
clearTimeout(timeout)
}
@@ -52,45 +39,30 @@
$: lastInput && $worldStore && debounce(handleConnection)
$: lastInput &&
lastInput.type == 'template' &&
$stateId &&
$state &&
debounce(async () => {
value = await getValue(lastInput)
dispatch('done')
})
debounce(() => (value = getValue(lastInput)))
$: lastInput &&
lastInput.type == 'eval' &&
$stateId &&
$state &&
debounce(async () => (value = await evalExpr(lastInput)))
debounce(() => (value = evalExpr(lastInput)))
async function handleConnection() {
console.log('handleConnection', lastInput)
function handleConnection() {
if (lastInput.type === 'connected') {
$worldStore?.connect<any>(lastInput, onValueChange, `${id}-${key}`)
$worldStore?.connect<any>(lastInput, onValueChange)
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
value = await getValue(lastInput)
value = getValue(lastInput)
} else if (lastInput.type == 'eval') {
value = await evalExpr(lastInput as EvalAppInput)
value = evalExpr(lastInput as EvalAppInput)
} else if (lastInput.type == 'upload') {
value = (lastInput as UploadAppInput).value
} else {
value = undefined
}
dispatch('done')
}
async function evalExpr(input: EvalAppInput) {
function evalExpr(input: EvalAppInput) {
try {
const r = await eval_like(
input.expr,
computeGlobalContext($worldStore, id, extraContext),
true,
$state,
$mode == 'dnd',
$componentControl,
$worldStore
)
const r = eval_like(input.expr, computeGlobalContext())
error = ''
return r
} catch (e) {
@@ -99,18 +71,23 @@
}
}
async function getValue(input: AppInput) {
function computeGlobalContext() {
return Object.fromEntries(
Object.entries($worldStore?.outputsById ?? {})
.filter(([k, _]) => k != id)
.map(([key, value]) => {
return [
key,
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
]
})
)
}
export function getValue(input: AppInput) {
if (input.type === 'template' && isCodeInjection(input.eval)) {
try {
const r = await eval_like(
'`' + input.eval + '`',
computeGlobalContext($worldStore, id, extraContext),
true,
$state,
$mode == 'dnd',
$componentControl,
$worldStore
)
const r = eval_like('`' + input.eval + '`', computeGlobalContext())
error = ''
return r
} catch (e) {
@@ -123,6 +100,31 @@
}
}
function create_context_function_template(eval_string, context) {
return `
return function (context) {
"use strict";
${
Object.keys(context).length > 0
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
: ``
}
return ${eval_string};
}
`
}
function make_context_evaluator(eval_string, context) {
let template = create_context_function_template(eval_string, context)
let functor = Function(template)
return functor()
}
function eval_like(text, context = {}) {
let evaluator = make_context_evaluator(text, context)
return evaluator(context)
}
function onValueChange(newValue: any): void {
if (lastInput.type === 'connected' && newValue !== undefined && newValue !== null) {
const { connection } = lastInput

View File

@@ -15,7 +15,7 @@
// Sync the result to the output
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
$: outputs = $worldStore?.outputsById?.[id] as {
$: outputs = $worldStore?.outputsById[id] as {
loading: Output<boolean>
result: Output<any>
}
@@ -25,7 +25,7 @@
}
function setOutput(v: any) {
outputs?.result?.set(v, true)
outputs.result?.set(v, true)
}
$: result && outputs && setOutput(result)

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { RefreshCw } from 'lucide-svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
import { RefreshCcw, RefreshCw } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../../types'
@@ -14,7 +16,6 @@
}
let loading = false
$: $worldStore?.outputsById[componentId]?.['loading']?.subscribe({
id: 'refresh-' + componentId,
next: (value) => {
loading = value
}

View File

@@ -5,7 +5,7 @@
export let id: string
export let shouldWrap: boolean = false
const { app, breakpoint, mode } = getContext<AppViewerContext>('AppViewerContext')
const { app, breakpoint } = getContext<AppViewerContext>('AppViewerContext')
$: gridItem = findGridItem($app, id)
@@ -16,12 +16,13 @@
const wrapperHeight = wrapper.getBoundingClientRect().height
const width = $breakpoint === 'sm' ? 3 : 12
gridItem[width].h = Math.ceil(wrapperHeight / 36)
gridItem = gridItem
}
}
</script>
{#if shouldWrap && $mode !== 'preview'}
<div class="h-full w-full" bind:this={wrapper}>
{#if shouldWrap}
<div bind:this={wrapper}>
<slot />
</div>
{:else}

View File

@@ -2,18 +2,17 @@
import { goto } from '$app/navigation'
import type { Schema } from '$lib/common'
import Alert from '$lib/components/common/alert/Alert.svelte'
import LightweightSchemaForm from '$lib/components/LightweightSchemaForm.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 { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
import { Bug } from 'lucide-svelte'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import { Bug, Loader2 } from 'lucide-svelte'
import { getContext, onMount } from 'svelte'
import { fade } from 'svelte/transition'
import type { AppInputs, Runnable } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext } from '../../types'
import { computeGlobalContext, eval_like } from './eval'
import InputValue from './InputValue.svelte'
import RefreshButton from './RefreshButton.svelte'
@@ -25,14 +24,13 @@
export let autoRefresh: boolean = true
export let result: any = undefined
export let forceSchemaDisplay: boolean = 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
export let render: boolean
export let recomputable: boolean = false
export let recomputeIds: string[] = []
const {
worldStore,
@@ -44,28 +42,17 @@
noBackend,
errorByComponent,
mode,
stateId,
state,
componentControl
stateId
} = getContext<AppViewerContext>('AppViewerContext')
$: autoRefresh && handleAutorefresh()
if (recomputable) {
$runnableComponents[id] = async () => {
await executeComponent(true)
}
$runnableComponents = $runnableComponents
}
function handleAutorefresh() {
if (autoRefresh && $worldStore) {
onMount(() => {
if (autoRefresh) {
$runnableComponents[id] = async () => {
await executeComponent(true)
}
executeComponent(true)
}
}
})
let args: Record<string, any> | undefined = undefined
let testIsLoading = false
@@ -93,23 +80,30 @@
$: fields && (currentStaticValues = computeStaticValues())
$: if (JSON.stringify(currentStaticValues) != JSON.stringify(lazyStaticValues)) {
lazyStaticValues = currentStaticValues
refreshIfAutoRefresh()
}
$: fields && (lazyStaticValues = computeStaticValues())
$: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh()
function refreshIfAutoRefresh() {
if (autoRefresh) {
setDebouncedExecute()
}
}
$: fields && (lazyStaticValues = computeStaticValues())
$: (runnableInputValues || extraQueryParams || args) &&
autoRefresh &&
testJobLoader &&
setDebouncedExecute()
// Test job internal state
let testJob: CompletedJob | undefined = undefined
let testJobLoader: TestJobLoader | undefined = undefined
let outputs = initOutput($worldStore, id, { result: undefined, loading: false })
$: outputs = $worldStore?.outputsById[id] as {
result: Output<Array<any>>
loading: Output<boolean>
}
$: if (outputs?.loading != undefined) {
outputs.loading.set(false, true)
}
$: outputs?.loading?.set(testIsLoading)
$: schemaStripped = stripSchema(fields, $stateId)
@@ -139,27 +133,17 @@
return schemaStripped as Schema
}
async function executeComponent(noToast = false) {
if (runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend') {
outputs?.loading?.set(true)
try {
const r = await eval_like(
runnable.inlineScript?.content,
computeGlobalContext($worldStore, id, {}),
false,
$state,
$mode == 'dnd',
$componentControl,
$worldStore
)
setResult(r)
$state = $state
} catch (e) {
sendUserToast('Error running frontend script: ' + e.message, true)
$: disabledArgs = Object.keys(fields ?? {}).reduce(
(disabledArgsAccumulator: string[], inputName: string) => {
if (fields[inputName].type === 'static') {
disabledArgsAccumulator = [...disabledArgsAccumulator, inputName]
}
outputs?.loading?.set(false)
return
}
return disabledArgsAccumulator
},
[]
)
async function executeComponent(noToast = false) {
if (noBackend) {
if (!noToast) {
sendUserToast('This app is not connected to a windmill backend, it is a static preview')
@@ -232,52 +216,15 @@
}
}
function setResult(res: any) {
outputs.result?.set(res)
result = res
const previousJobId = Object.keys($errorByComponent).find(
(key) => $errorByComponent[key].componentId === id
)
if (previousJobId && !result?.error) {
delete $errorByComponent[previousJobId]
$errorByComponent = $errorByComponent
}
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(gotoUrl)
}
}
if (recomputeIds) {
recomputeIds.map((id) => $runnableComponents?.[id]?.())
}
}
$: result?.error && recordError(result.error)
</script>
{#each Object.entries(fields ?? {}) as [key, v] (key)}
{#each Object.entries(fields ?? {}) as [key, v]}
{#if v.type != 'static' && v.type != 'user'}
<InputValue {key} {id} input={fields[key]} bind:value={runnableInputValues[key]} />
<InputValue {id} input={fields[key]} bind:value={runnableInputValues[key]} />
{/if}
{/each}
{#if runnable?.type == 'runnableByName' && runnable.inlineScript?.language == 'frontend'}
{#each runnable.inlineScript.refreshOn ?? [] as { id: tid, key } (`${tid}-${key}`)}
{@const fkey = `${tid}-${key}`}
<InputValue
{id}
key={fkey}
input={{ type: 'connected', connection: { componentId: tid, path: key }, fieldType: 'any' }}
bind:value={runnableInputValues[fkey]}
/>
{/each}
{/if}
<TestJobLoader
workspaceOverride={workspace}
on:done={(e) => {
@@ -285,7 +232,24 @@
const startedAt = new Date(testJob.started_at).getTime()
if (startedAt > lastStartedAt) {
lastStartedAt = startedAt
setResult(e.detail.result)
outputs.result?.set(testJob?.result)
result = testJob.result
const previousJobId = Object.keys($errorByComponent).find(
(key) => $errorByComponent[key].componentId === id
)
if (previousJobId && !result?.error) {
delete $errorByComponent[previousJobId]
$errorByComponent = $errorByComponent
}
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(gotoUrl)
}
}
}
}
}}
@@ -298,7 +262,14 @@
<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">
<LightweightSchemaForm schema={schemaStripped} bind:args />
<SchemaForm
{flexWrap}
schema={schemaStripped}
bind:args
{disabledArgs}
shouldHideNoInputs
noVariablePicker
/>
</div>
{/if}
@@ -342,4 +313,6 @@
</div>
{/if}
</div>
{:else}
<div class="w-full h-full" />
{/if}

View File

@@ -15,12 +15,12 @@
export let autoRefresh: boolean = true
export let runnableComponent: RunnableComponent | undefined = undefined
export let forceSchemaDisplay: boolean = false
export let flexWrap = false
export let runnableClass = ''
export let runnableStyle = ''
export let goto: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
export let render: boolean
export let recomputeIds: string[] = []
const { staticExporter, noBackend } = getContext<AppViewerContext>('AppViewerContext')
@@ -44,9 +44,9 @@
<slot />
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
<RunnableComponent
{recomputeIds}
gotoUrl={goto}
{gotoNewTab}
{flexWrap}
bind:this={runnableComponent}
fields={componentInput.fields}
bind:result

View File

@@ -1,125 +0,0 @@
import { sendUserToast } from '$lib/utils'
import { isPlainObject } from 'lodash'
import type { World } from '../../rx'
export function computeGlobalContext(
world: World | undefined,
id: string | undefined,
extraContext: any = {}
) {
return {
...Object.fromEntries(
Object.entries(world?.outputsById ?? {})
.filter(([k, _]) => k != id && k != 'state')
.map(([key, value]) => {
return [
key,
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
]
})
),
...extraContext
}
}
function create_context_function_template(eval_string, context, noReturn: boolean) {
return `
return async function (context, state, goto, setTab) {
"use strict";
${
Object.keys(context).length > 0
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
: ``
}
${noReturn ? `return ${eval_string}` : eval_string}
}
`
}
function make_context_evaluator(
eval_string,
context,
noReturn: boolean
): (context, state, goto, setTab) => Promise<any> {
let template = create_context_function_template(eval_string, context, noReturn)
let functor = Function(template)
return functor()
}
function isSerializable(obj) {
var isNestedSerializable
function isPlain(val) {
return (
val == null ||
typeof val === 'undefined' ||
typeof val === 'string' ||
typeof val === 'boolean' ||
typeof val === 'number' ||
Array.isArray(val) ||
isPlainObject(val)
)
}
if (!isPlain(obj)) {
return false
}
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (!isPlain(obj[property])) {
return false
}
if (typeof obj[property] == 'object') {
isNestedSerializable = isSerializable(obj[property])
if (!isNestedSerializable) {
return false
}
}
}
}
return true
}
export async function eval_like(
text: string,
context = {},
noReturn: boolean,
state: Record<string, any>,
editor: boolean,
controlComponents: Record<string, { setTab?: (index: number) => void }>,
worldStore: World | undefined
) {
const proxiedState = new Proxy(state, {
set(target, key, value) {
if (typeof key !== 'string') {
throw new Error('Invalid key')
}
let o = worldStore?.newOutput('state', key, value)
if (isSerializable(value)) {
o?.set(value, true)
} else {
o?.set('Not serializable object usable only by frontend scripts', true)
}
target[key] = value
return true
}
})
let evaluator = make_context_evaluator(text, context, noReturn)
return await evaluator(
context,
proxiedState,
async (x, newTab) => {
if (newTab || editor) {
if (!newTab) {
sendUserToast(
'In editor mode, `goto` opens a new tab to prevent losing your work. To test the redirection , use the preview mode.'
)
}
window.open(x, '_blank')
} else {
await newTab(x)
}
},
(id, index) => {
controlComponents[id]?.setTab?.(index)
}
)
}

View File

@@ -1,21 +1,15 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
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
@@ -23,15 +17,17 @@
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
export const staticOutputs: string[] = ['result']
let defaultValue: boolean | undefined = undefined
let labelValue: string = 'Default label'
// As the checkbox is a special case and has no input
// we need to manually set the output
let outputs = initOutput($worldStore, id, {
result: false
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<boolean>
}
$: defaultValue != undefined && outputs?.result.set(defaultValue)

View File

@@ -1,16 +1,18 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let inputType: 'date'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
@@ -22,9 +24,9 @@
let value: string | undefined = undefined
let outputs = initOutput($worldStore, id, {
result: undefined as string | undefined
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string | undefined>
}
$: handleDefault(defaultValue)

View File

@@ -2,15 +2,15 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { FileInput } from '../../../common'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
@@ -20,9 +20,9 @@
let allowMultiple: boolean | undefined = undefined
let text: string | undefined = undefined
let outputs = initOutput($worldStore, id, {
result: [] as string[] | undefined
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string[] | undefined>
}
// Receives Base64 encoded strings from the input component
async function handleChange(files: string[] | undefined) {

View File

@@ -3,15 +3,15 @@
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { initOutput } from '../../editor/appUtils'
export const staticOutputs: string[] = ['result']
export let id: string
export let configuration: RichConfigurations
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<'input'> | undefined = undefined
@@ -22,9 +22,9 @@
let items: { label: string; value: string }[]
let placeholder: string = 'Select an item'
let outputs = initOutput($worldStore, id, {
result: [] as string[]
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string[] | undefined>
}
// $: outputs && handleOutputs()

View File

@@ -1,15 +1,17 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
@@ -23,9 +25,9 @@
let max: number | undefined = undefined
let step = 1
let outputs = initOutput($worldStore, id, {
result: undefined as number | undefined
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<number | undefined>
}
$: handleDefault(defaultValue)

View File

@@ -3,16 +3,16 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'handles' | 'bar' | 'limits' | 'values'> | undefined =
undefined
export let render: boolean
@@ -25,11 +25,9 @@
let values: [number, number] = [0, 0]
let outputs = initOutput($worldStore, id, {
result: null as [number, number] | null
})
console.log('reset')
$: outputs = $worldStore?.outputsById[id] as {
result: Output<[number, number] | null>
}
$: outputs?.result.set(values)

View File

@@ -1,16 +1,17 @@
<script lang="ts">
import { getContext } from 'svelte'
import Select from 'svelte-select'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { initOutput } from '../../editor/appUtils'
export const staticOutputs: string[] = ['result']
export let id: string
export let configuration: RichConfigurations
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<'input'> | undefined = undefined
@@ -21,9 +22,9 @@
let items: { label: string; value: any; created?: boolean }[]
let placeholder: string = 'Select an item'
let outputs = initOutput($worldStore, id, {
result: undefined
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<any | undefined>
}
let value: string | undefined = outputs?.result.peak()
@@ -42,8 +43,6 @@
: []
if (defaultValue) {
value = JSON.stringify(defaultValue)
} else if (listItems.length > 0) {
value = listItems[0]?.value
}
}
@@ -62,7 +61,6 @@
try {
result = JSON.parse(e.detail?.['value'])
} catch (_) {}
value = result
outputs?.result.set(result)
}

View File

@@ -3,16 +3,16 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { twMerge } from 'tailwind-merge'
import { concatCustomCss } from '../../utils'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value' | 'bar'> | undefined =
undefined
export let render: boolean
@@ -23,9 +23,9 @@
let step = 1
let slider: HTMLElement
let outputs = initOutput($worldStore, id, {
result: (0 as number) || null
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<number | null>
}
let values: [number] = [outputs?.result.peak() ?? 0]

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let inputType = 'text'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let appCssKey: 'textinputcomponent' | 'passwordinputcomponent' | 'emailinputcomponent' =
'textinputcomponent'
@@ -24,13 +24,13 @@
let defaultValue: string | undefined = undefined
let value: string | undefined = undefined
let outputs = initOutput($worldStore, id, {
result: ''
})
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string | undefined>
}
$: handleDefault(defaultValue)
$: outputs?.result.set(value ?? '')
$: outputs?.result.set(value)
function handleDefault(defaultValue: string | undefined) {
value = defaultValue

View File

@@ -1,27 +1,23 @@
<script lang="ts">
import { initOutput } from '$lib/components/apps/editor/appUtils'
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../../inputType'
import type { Output } from '../../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../../types'
import { concatCustomCss } from '../../../utils'
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
import InputValue from '../../helpers/InputValue.svelte'
import CurrencyInput from './CurrencyInput.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined
})
let defaultValue: number | undefined = undefined
let isNegativeAllowed: boolean | undefined = undefined

View File

@@ -4,12 +4,17 @@
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, GridItem } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
let noPadding: boolean | undefined = undefined
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
function onFocus() {
@@ -24,10 +29,13 @@
$: css = concatCustomCss($app.css?.containercomponent, customCss)
</script>
<div class="w-full h-full">
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={render}
{noPadding}
{id}
class={css?.container.class}
style={css?.container.style}

View File

@@ -6,7 +6,6 @@
AppViewerContext,
ComponentCustomCSS,
HorizontalAlignment,
RichConfigurations,
VerticalAlignment
} from '../../types'
import { concatCustomCss } from '../../utils'
@@ -14,7 +13,7 @@
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'divider'> | undefined = undefined

View File

@@ -2,7 +2,7 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import Portal from 'svelte-portal'
import { concatCustomCss } from '../../utils'
@@ -12,7 +12,7 @@
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
@@ -21,6 +21,7 @@
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let gridContent: string[] | undefined = undefined
let noPadding: boolean | undefined = undefined
let drawerTitle: string | undefined = undefined
let appDrawer: Drawer
@@ -61,6 +62,7 @@
</div>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue {id} input={configuration.drawerTitle} bind:value={drawerTitle} />
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.color} bind:value={color} />
@@ -79,6 +81,7 @@
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{noPadding}
{id}
class={css?.container.class}
style={css?.container.style}

View File

@@ -2,26 +2,25 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type {
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
RichConfigurations
} from '../../types'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { deepEqual } from 'fast-equals'
export let id: string
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let horizontal: boolean = false
export let panes: number[]
export let render: boolean
const { app, focusedGrid, selectedComponent, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
let noPadding: boolean | undefined = undefined
function onFocus() {
$focusedGrid = {
@@ -67,6 +66,8 @@
}
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div class="h-full w-full border" on:pointerdown|stopPropagation>
<Splitpanes {horizontal}>
{#each sumedup as paneSize, index (index)}
@@ -74,12 +75,16 @@
{#if $app.subgrids?.[`${id}-${index}`]}
<SubGridEditor
visible={render}
noYPadding
{noPadding}
{id}
shouldHighlight={$focusedGrid?.subGridIndex === index}
class={css?.container.class}
style={css?.container.style}
bind:subGrid={$app.subgrids[`${id}-${index}`]}
containerHeight={horizontal ? undefined : componentContainerHeight - 8}
containerHeight={horizontal
? (componentContainerHeight * paneSize) / 100 - 1
: componentContainerHeight - 8}
on:focus={() => {
$selectedComponent = id
$focusedGrid = {

View File

@@ -1,33 +1,31 @@
<script lang="ts">
import { Tab, Tabs } from '$lib/components/common'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import { getContext, onMount } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import type { Output } from '../../rx'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
export let tabs: string[]
export let customCss:
| ComponentCustomCSS<'tabRow' | 'tabs' | 'selectedTab' | 'container'>
| undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = configuration.tabsKind != undefined
const { app, worldStore, focusedGrid, selectedComponent, mode, componentControl } =
export const staticOutputs: string[] = ['selectedTabIndex']
const { app, worldStore, focusedGrid, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
let selected: string = tabs[0]
let kind: 'tabs' | 'sidebar' | 'invisibleOnView' | undefined = undefined
let tabHeight: number = 0
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
let outputs = initOutput($worldStore, id, {
selectedTabIndex: 0
})
let selected: string = tabs[0]
let noPadding: boolean | undefined = undefined
let tabHeight: number = 0
function handleTabSelection() {
const selectedIndex = tabs?.indexOf(selected)
@@ -73,75 +71,52 @@
return true
}
return false
},
setTab: (tab: number) => {
selected = tabs[tab]
}
}
$: selected && handleTabSelection()
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
let subgridWidth: number | undefined = undefined
$: outputs = $worldStore?.outputsById[id] as {
selectedTabIndex: Output<number>
}
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
</script>
<InputValue
on:done={() => initializing && (initializing = false)}
{id}
input={configuration.tabsKind}
bind:value={kind}
/>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div class={kind == 'sidebar' ? 'flex gap-4 w-full' : 'w-full'}>
{#if !kind || kind == 'tabs' || (kind == 'invisibleOnView' && $mode == 'dnd')}
<div bind:clientHeight={tabHeight}>
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
{#each tabs ?? [] as res}
<Tab
value={res}
class={css?.tabs?.class}
style={css?.tabs?.style}
selectedClass={css?.selectedTab?.class}
selectedStyle={css?.selectedTab?.style}
>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</Tabs>
</div>
{:else if kind == 'sidebar'}
<div
class="flex gap-y-2 flex-col w-1/6 max-w-[160px] bg-white text-[#2e3440] opacity-80 px-4 pt-4 border-r border-gray-400"
>
<div>
<div bind:clientHeight={tabHeight} on:pointerdown|stopPropagation>
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
{#each tabs ?? [] as res}
<button
class="rounded-sm !truncate text-sm hover:bg-gray-100 hover:border hover:text-black px-1 py-2 {selected ==
res
? 'outline outline-gray-500 outline-1 bg-white text-black'
: ''}"
on:pointerdown|stopPropagation
on:click={() => (selected = res)}>{res}</button
<Tab
value={res}
class={css?.tabs?.class}
style={css?.tabs?.style}
selectedClass={css?.selectedTab?.class}
selectedStyle={css?.selectedTab?.style}
>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</div>
{/if}
<div bind:clientWidth={subgridWidth} class="w-full">
{#if $app.subgrids}
{#each tabs ?? [] as res, i}
<SubGridEditor
{id}
visible={render && i === selectedIndex}
bind:subGrid={$app.subgrids[`${id}-${i}`]}
class={css?.container?.class}
style={css?.container?.style}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/each}
{/if}
</Tabs>
</div>
{#if $app.subgrids}
{#each tabs ?? [] as res, i}
<SubGridEditor
{id}
visible={render && i === selectedIndex}
bind:subGrid={$app.subgrids[`${id}-${i}`]}
{noPadding}
class={css?.container?.class}
style={css?.container?.style}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/each}
{/if}
</div>

View File

@@ -27,8 +27,7 @@
id={component.id}
shouldCapitalize={false}
bind:inputSpecs={component.componentInput.fields}
userInputEnabled={component.type === 'formcomponent' ||
component.type === 'formbuttoncomponent'}
userInputEnabled={component.type !== 'buttoncomponent'}
{resourceOnly}
/>
{/if}

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