Compare commits

..

4 Commits

Author SHA1 Message Date
Ruben Fiszel
b1d83d38dd all 2023-11-28 15:25:07 +01:00
Ruben Fiszel
d9d0dafe45 Merge branch 'main' into rf/flow 2023-11-27 21:54:10 +01:00
Ruben Fiszel
18b5bc6bd3 fix: improve autocomplete reactivity 2023-11-27 18:53:26 +01:00
Ruben Fiszel
60ff7d06b0 flow 2023-11-27 18:42:59 +01:00
97 changed files with 1163 additions and 15944 deletions

View File

@@ -15,7 +15,7 @@ on:
jobs:
cargo_test:
runs-on: ubicloud-standard-8
runs-on: [self-hosted, new]
container:
image: ghcr.io/windmill-labs/backend-tests
services:
@@ -31,16 +31,15 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions-rust-lang/setup-rust-toolchain@v1
# - uses: Swatinem/rust-cache@v2
# with:
# workspaces: |
# backend
# backend -> target
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
backend
backend -> target
- name: cargo test
timeout-minutes: 15
timeout-minutes: 10
run:
mkdir frontend/build && cd backend && touch
windmill-api/openapi-deref.yaml &&
DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill
DISABLE_EMBEDDING=true cargo test --features enterprise --all --
--nocapture
DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill DISABLE_EMBEDDING=true cargo
test --features enterprise --all -- --nocapture

View File

@@ -7,13 +7,14 @@ on:
jobs:
benchmark:
runs-on: ubicloud-standard-8
runs-on: [self-hosted, new]
services:
postgres:
image: postgres
env:
POSTGRES_DB: windmill
POSTGRES_PASSWORD: changeme
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s
--health-retries 5
@@ -34,12 +35,12 @@ jobs:
image: ghcr.io/windmill-labs/windmill-ee:main
env:
DATABASE_URL: postgres://postgres:changeme@postgres:5432/windmill
MODE: worker
DISABLE_SERVER: true
WORKER_GROUP: dedicated
DEDICATED_WORKER: "admins:f/benchmarks/dedicated"
LICENSE_KEY: ${{ secrets.WM_LICENSE_KEY_CI }}
options: >-
--pull always --restart unless-stopped
--pull always
steps:
- uses: denoland/setup-deno@v1
with:
@@ -49,11 +50,9 @@ jobs:
ref: benchmarks
- name: benchmark
timeout-minutes: 20
run:
deno run --unstable -A -r
run: deno run --unstable -A -r
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/benchmark_suite.ts
-c
https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json
-c https://raw.githubusercontent.com/windmill-labs/windmill/${GITHUB_REF##ref/head/}/benchmarks/suite_config.json
- name: Push changes
run: |
pwd

View File

@@ -6,7 +6,7 @@ on:
- "version.txt"
jobs:
change_version:
runs-on: ubicloud
runs-on: ubuntu-latest
container: node:18
steps:
- uses: actions/checkout@v3

13
.github/workflows/clean-docker.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Clean docker
on:
schedule:
# * is a special character in YAML so you have to quote this string
- cron: "0 0 */2 * *"
jobs:
build:
runs-on: [self-hosted, new]
steps:
- name: clean docker
run: |
sudo docker system prune -f

View File

@@ -8,7 +8,7 @@ env:
jobs:
build_deno_and_push_to_repo:
runs-on: ubicloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: generate_deno
@@ -31,7 +31,7 @@ jobs:
tag_repo:
needs: [build_deno_and_push_to_repo]
runs-on: ubicloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:

View File

@@ -16,7 +16,7 @@ permissions:
jobs:
build_ee:
runs-on: ubicloud
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:

View File

@@ -20,7 +20,7 @@ permissions:
jobs:
build:
runs-on: ubicloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
@@ -63,7 +63,7 @@ jobs:
org.opencontainers.image.licenses=AGPLv3
build_ee:
runs-on: ubicloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
@@ -143,7 +143,7 @@ jobs:
deploy_s3:
needs: [build_ee]
runs-on: ubicloud
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
@@ -165,9 +165,10 @@ jobs:
bucket: windmill-frontend
bucket-region: us-east-1
attach_amd64_binary_to_release:
needs: [build, build_ee]
runs-on: ubicloud
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') }}
env:
ARCH: amd64
@@ -202,6 +203,7 @@ jobs:
mv "${{ steps.extract.outputs.destination }}/windmill" "${{ steps.extract.outputs.destination }}/windmill-${ARCH}"
mv "${{ steps.extract-ee.outputs.destination }}/windmill" "${{ steps.extract-ee.outputs.destination }}/windmill-ee-${ARCH}"
- name: Attach binary to release
uses: softprops/action-gh-release@v1
with:
@@ -211,7 +213,7 @@ jobs:
attach_arm64_binary_to_release:
needs: [build, build_ee]
runs-on: ubicoud
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') }}
env:
ARCH: arm64
@@ -246,6 +248,7 @@ jobs:
mv "${{ steps.extract.outputs.destination }}/windmill" "${{ steps.extract.outputs.destination }}/windmill-${ARCH}"
mv "${{ steps.extract-ee.outputs.destination }}/windmill" "${{ steps.extract-ee.outputs.destination }}/windmill-ee-${ARCH}"
- name: Attach binary to release
uses: softprops/action-gh-release@v1
with:
@@ -255,7 +258,7 @@ jobs:
publish_ecr:
needs: [build_ee]
runs-on: ubicloud
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v3

View File

@@ -5,6 +5,8 @@ env:
name: Build windmill:mssql
on:
workflow_dispatch:
schedule:
- cron: "0 0 */4 * *"
concurrency:
group: ${{ github.ref }}-mssql

View File

@@ -1,14 +1,14 @@
name: check frontend build
on:
pull_request:
types: [opened, synchronize, reopened, closed]
types: [opened,synchronize,reopened,closed]
paths:
- "frontend/**"
merge_group:
jobs:
npm_check:
runs-on: ubicloud-standard-8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
@@ -16,6 +16,4 @@ jobs:
node-version: 18
- name: "npm check"
timeout-minutes: 2
run:
cd frontend && npm ci && npm run generate-backend-client && npm run
check
run: cd frontend && npm ci && npm run generate-backend-client && npm run check

View File

@@ -10,7 +10,7 @@ env:
jobs:
build_go_and_push_to_repo:
runs-on: ubicloud-standard-8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4

View File

@@ -6,7 +6,7 @@ on:
jobs:
build_npm:
runs-on: ubicloud-standard-8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3

View File

@@ -16,17 +16,14 @@ permissions:
packages: write
jobs:
sleep:
runs-on: ubicloud
publish_lsp:
runs-on: ubuntu-latest
steps:
- name: Sleep for 900 seconds waiting for pypi to update index
if: startsWith(github.ref, 'refs/tags/v')
run: sleep 900
shell: bash
publish_lsp:
needs: [sleep]
runs-on: ubicloud
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
@@ -65,7 +62,7 @@ jobs:
publish_lsp_private:
needs: [publish_lsp]
runs-on: ubicloud-standard-8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:

View File

@@ -7,7 +7,7 @@ on:
jobs:
publish_pypi:
runs-on: ubicloud-standard-8
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
container:
image: ghcr.io/windmill-labs/python-client-builder

View File

@@ -6,7 +6,7 @@ name: release-please
jobs:
release-please:
name: "Release please"
runs-on: ubicloud-standard-8
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v3
with:

View File

@@ -1,34 +1,6 @@
# Changelog
## [1.217.0](https://github.com/windmill-labs/windmill/compare/v1.216.0...v1.217.0) (2023-11-29)
### Features
* add support for raw query args ([8275602](https://github.com/windmill-labs/windmill/commit/82756023728fe392fee93a7bba0567ce582aad8c))
* **frontend:** add "hide schedules" filter ([#2710](https://github.com/windmill-labs/windmill/issues/2710)) ([46e0f91](https://github.com/windmill-labs/windmill/commit/46e0f913878c9f688594cfe1c9184438e4facf49))
* **frontend:** Added tailwind classes auto-complete ([#2712](https://github.com/windmill-labs/windmill/issues/2712)) ([2d3ce8a](https://github.com/windmill-labs/windmill/commit/2d3ce8a49c952accb4f1ceb0e6ea608df539f179))
* **frontend:** Stat card improvement ([#2709](https://github.com/windmill-labs/windmill/issues/2709)) ([89abb68](https://github.com/windmill-labs/windmill/commit/89abb68f635d062778485c15d88fd6272ebd40a2))
* scheduled app reports ([#2714](https://github.com/windmill-labs/windmill/issues/2714)) ([3789b34](https://github.com/windmill-labs/windmill/commit/3789b34dae72ff57bb9e0bb7e93439446c78095d))
### Bug Fixes
* ai fix popup placement + update edit/fix prompt to return complete code ([#2715](https://github.com/windmill-labs/windmill/issues/2715)) ([76a387f](https://github.com/windmill-labs/windmill/commit/76a387f4a181d21afad0138c07f947aa292978cc))
* better error for moved openai resource ([#2724](https://github.com/windmill-labs/windmill/issues/2724)) ([15b2c9f](https://github.com/windmill-labs/windmill/commit/15b2c9f171122ab074f8430a785f8ed577921fcc))
* Deno can talk to private NPM registries behind HTTPS ([#2713](https://github.com/windmill-labs/windmill/issues/2713)) ([726866b](https://github.com/windmill-labs/windmill/commit/726866b410863ca9583a8145ebd9f4c4557cef08))
* Error handler now supports flows ([#2707](https://github.com/windmill-labs/windmill/issues/2707)) ([36e46e2](https://github.com/windmill-labs/windmill/commit/36e46e2e47e1e949e1e235fa4ec0365b46d80de1))
* **frontend:** fix separator z-index ([#2720](https://github.com/windmill-labs/windmill/issues/2720)) ([9525ab7](https://github.com/windmill-labs/windmill/commit/9525ab7bbab9f820d1f6fee44f9a2e9ad19d1b54))
* **frontend:** Fix table initial ordering ([#2727](https://github.com/windmill-labs/windmill/issues/2727)) ([1a1d1db](https://github.com/windmill-labs/windmill/commit/1a1d1db96fd069434291f4b5f37ae1d0e21c5e44))
* **frontend:** Improve error message + fix overflow when file name is too long ([#2691](https://github.com/windmill-labs/windmill/issues/2691)) ([c990f85](https://github.com/windmill-labs/windmill/commit/c990f856aaf821899cc54db20a653af396ceae7f))
* generate cargo lock file ([#2722](https://github.com/windmill-labs/windmill/issues/2722)) ([bd31979](https://github.com/windmill-labs/windmill/commit/bd31979a62a30071e68385b5eb5dc28f37ea8fb5))
* improve autocomplete reactivity ([c3eaf0b](https://github.com/windmill-labs/windmill/commit/c3eaf0bf4aab531b52a1b3ac276c6840b0925786))
* make dedicated workers able to redeploy automatically ([e6d67f4](https://github.com/windmill-labs/windmill/commit/e6d67f4e5994360e7ae83c1303b31514b6062d1d))
* minor fixes to private NPM and python registries to get everything working ([#2728](https://github.com/windmill-labs/windmill/issues/2728)) ([3d6fb15](https://github.com/windmill-labs/windmill/commit/3d6fb15a90f4d5aa18a6d6158760a380295e3d9e))
* only list session and permanent token in user settings ([c8046af](https://github.com/windmill-labs/windmill/commit/c8046af9d01ade6891ea97e56974bb742bbf3e6e))
* trim .bun.ts for local imports ([d2b3026](https://github.com/windmill-labs/windmill/commit/d2b3026032d288e1e5de1f37979d92d02094b3e4))
## [1.216.0](https://github.com/windmill-labs/windmill/compare/v1.215.0...v1.216.0) (2023-11-26)

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes FROM token WHERE email = $1\n ORDER BY created_at DESC",
"query": "SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes FROM token WHERE email = $1\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
@@ -48,5 +48,5 @@
true
]
},
"hash": "9ce6eecfa10c2f71cc536957ead498ad829ea8023ce449eab27225ec66738525"
"hash": "773c145013623e4eb29a8df70e46d805dc5b15942a36a0d988521abb0cb34e41"
}

View File

@@ -1,52 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at, scopes FROM token WHERE email = $1 AND label != 'ephemeral-script'\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "label",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "token_prefix",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "expiration",
"type_info": "Timestamptz"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "last_used_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"name": "scopes",
"type_info": "TextArray"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
true,
null,
true,
false,
false,
true
]
},
"hash": "bc1f2f169b4960dae02f62e37bb4ae081146e598109860ee1f42cd3778c5ebaf"
}

322
backend/Cargo.lock generated
View File

@@ -559,9 +559,9 @@ dependencies = [
[[package]]
name = "atoi_simd"
version = "0.15.5"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccfc14f5c3e34de57539a7ba9c18ecde3d9bbde48d232ea1da3e468adb307fd0"
checksum = "fc41b65e01b6851bdcd2d741824e6b310d571396bf3915e31e4792034ee65126"
[[package]]
name = "atomic-waker"
@@ -571,11 +571,11 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atomic-write-file"
version = "0.1.1"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae364a6c1301604bbc6dfbf8c385c47ff82301dd01eef506195a029196d8d04"
checksum = "c232177ba50b16fe7a4588495bd474a62a9e45a8e4ca6fd7d0b7ac29d164631e"
dependencies = [
"nix",
"nix 0.26.4",
"rand 0.8.5",
]
@@ -587,29 +587,29 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80c950a809d39bc9480207cb1cfc879ace88ea7e3a4392a8e9999e45d6e5692e"
checksum = "8e245d7c741a8e4b23133f36750c4bcb3938a66ac49510caaf8b83afd52db1ec"
dependencies = [
"aws-credential-types 1.0.1",
"aws-credential-types 1.0.0",
"aws-http 0.60.0",
"aws-runtime 1.0.1",
"aws-runtime 1.0.0",
"aws-sdk-sso",
"aws-sdk-ssooidc",
"aws-sdk-sts",
"aws-smithy-async 1.0.2",
"aws-smithy-async 1.0.1",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-runtime 1.0.2",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-types 1.0.1",
"aws-smithy-runtime 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"aws-types 1.0.0",
"bytes",
"fastrand 2.0.1",
"hex",
"http",
"hyper",
"ring 0.17.6",
"ring 0.17.5",
"time",
"tokio",
"tracing",
@@ -630,13 +630,13 @@ dependencies = [
[[package]]
name = "aws-credential-types"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1317e1a3514b103cf7d5828bbab3b4d30f56bd22d684f8568bc51b6cfbbb1c"
checksum = "e6dec6f3d42983be70a113f999476185e124884f43f4d60129c7157aede7bda1"
dependencies = [
"aws-smithy-async 1.0.2",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-smithy-async 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"zeroize",
]
@@ -663,9 +663,9 @@ version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361c4310fdce94328cc2d1ca0c8a48c13f43009c61d3367585685a50ca8c66b6"
dependencies = [
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-types 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"aws-types 1.0.0",
"bytes",
"http",
"http-body",
@@ -697,18 +697,18 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ed7ef604a15fd0d4d9e43701295161ea6b504b63c44990ead352afea2bc15e9"
checksum = "36ba3ad97d674bfaeed684d528a07cee6e81cbf16e6d6c7e272a130b5e71e6b9"
dependencies = [
"aws-credential-types 1.0.1",
"aws-credential-types 1.0.0",
"aws-http 0.60.0",
"aws-sigv4 1.0.1",
"aws-smithy-async 1.0.2",
"aws-sigv4 1.0.0",
"aws-smithy-async 1.0.1",
"aws-smithy-http 0.60.0",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-types 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"aws-types 1.0.0",
"fastrand 2.0.1",
"http",
"percent-encoding",
@@ -748,20 +748,20 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.3.0"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0619ab97a5ca8982e7de073cdc66f93e5f6a1b05afc09e696bec1cb3607cd4df"
checksum = "5786afe1fd164e53f108b2bd4982a31c5a821dc1677d05a12de65ebcc6ede52a"
dependencies = [
"aws-credential-types 1.0.1",
"aws-credential-types 1.0.0",
"aws-http 0.60.0",
"aws-runtime 1.0.1",
"aws-smithy-async 1.0.2",
"aws-runtime 1.0.0",
"aws-smithy-async 1.0.1",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-runtime 1.0.2",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-types 1.0.1",
"aws-smithy-runtime 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"aws-types 1.0.0",
"bytes",
"http",
"regex",
@@ -770,20 +770,20 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.3.0"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04b9f5474cc0f35d829510b2ec8c21e352309b46bf9633c5a81fb9321e9b1c7"
checksum = "31569ac7750ebc3097058c1e72d79576e16b3fb262aa0d6510188bff623f9804"
dependencies = [
"aws-credential-types 1.0.1",
"aws-credential-types 1.0.0",
"aws-http 0.60.0",
"aws-runtime 1.0.1",
"aws-smithy-async 1.0.2",
"aws-runtime 1.0.0",
"aws-smithy-async 1.0.1",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-runtime 1.0.2",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-types 1.0.1",
"aws-smithy-runtime 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"aws-types 1.0.0",
"bytes",
"http",
"regex",
@@ -792,22 +792,22 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.3.0"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5700da387716ccfc30b27f44b008f457e1baca5b0f05b6b95455778005e3432a"
checksum = "9b1d955bacd8c3637908a40a4af2f7a732461ee7d95ec02599a3610ee55781d7"
dependencies = [
"aws-credential-types 1.0.1",
"aws-credential-types 1.0.0",
"aws-http 0.60.0",
"aws-runtime 1.0.1",
"aws-smithy-async 1.0.2",
"aws-runtime 1.0.0",
"aws-smithy-async 1.0.1",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-query",
"aws-smithy-runtime 1.0.2",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-smithy-runtime 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"aws-smithy-xml 0.60.0",
"aws-types 1.0.1",
"aws-types 1.0.0",
"http",
"regex",
"tracing",
@@ -833,7 +833,7 @@ dependencies = [
"p256",
"percent-encoding",
"regex",
"ring 0.17.6",
"ring 0.17.5",
"sha2 0.10.8",
"time",
"tracing",
@@ -842,14 +842,14 @@ dependencies = [
[[package]]
name = "aws-sigv4"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380adcc8134ad8bbdfeb2ace7626a869914ee266322965276cbc54066186d236"
checksum = "b4d07e2f2fc32acb7423d054ec8ba2b361dafc95fabc5a211baf4a566571d20e"
dependencies = [
"aws-credential-types 1.0.1",
"aws-credential-types 1.0.0",
"aws-smithy-http 0.60.0",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"bytes",
"form_urlencoded",
"hex",
@@ -876,9 +876,9 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e37ca17d25fe1e210b6d4bdf59b81caebfe99f986201a1228cb5061233b4b13"
checksum = "1fbfa248f7f966d73e325dbc85851a5500042b6d96e3c3b535a8527707f36fe4"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -944,8 +944,8 @@ version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b1de8aee22f67de467b2e3d0dd0fb30859dc53f579a63bd5381766b987db644"
dependencies = [
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"bytes",
"bytes-utils",
"futures-core",
@@ -973,7 +973,7 @@ version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a46dd338dc9576d6a6a5b5a19bd678dcad018ececee11cf28ecd7588bd1a55c"
dependencies = [
"aws-smithy-types 1.0.2",
"aws-smithy-types 1.0.1",
]
[[package]]
@@ -982,7 +982,7 @@ version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb5b8c7a86d4b6399169670723b7e6f21a39fc833a30f5c5a2f997608178129"
dependencies = [
"aws-smithy-types 1.0.2",
"aws-smithy-types 1.0.1",
"urlencoding",
]
@@ -1012,17 +1012,16 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "273479291efc55e7b0bce985b139d86b6031adb8e50f65c1f712f20ba38f6388"
checksum = "064b808143d80b50744b1b22cce801238a545b84859c6cf8e275997252dd1d25"
dependencies = [
"aws-smithy-async 1.0.2",
"aws-smithy-async 1.0.1",
"aws-smithy-http 0.60.0",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"bytes",
"fastrand 2.0.1",
"h2",
"http",
"http-body",
"hyper",
@@ -1052,12 +1051,12 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6cebff0d977b6b6feed2fd07db52aac58ba3ccaf26cdd49f1af4add5061bef9"
checksum = "4d27c3235d4972ed976b5c1a82286e7c4457f618f3c2ae6d4ae44f081dd24575"
dependencies = [
"aws-smithy-async 1.0.2",
"aws-smithy-types 1.0.2",
"aws-smithy-async 1.0.1",
"aws-smithy-types 1.0.1",
"bytes",
"http",
"pin-project-lite",
@@ -1091,9 +1090,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7f48b3f27ddb40ab19892a5abda331f403e3cb877965e4e51171447807104af"
checksum = "d2fc32035dc0636a8583cf0c6dd7f1e6d5404103b836d26228b8730907a88d9f"
dependencies = [
"base64-simd",
"bytes",
@@ -1145,14 +1144,14 @@ dependencies = [
[[package]]
name = "aws-types"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8403fc56b1f3761e8efe45771ddc1165e47ec3417c68e68a4519b5cb030159ca"
checksum = "b18c0eb301cce69298555c4884795497c67b3bd511aa3f07470382f4bf3ef043"
dependencies = [
"aws-credential-types 1.0.1",
"aws-smithy-async 1.0.2",
"aws-smithy-runtime-api 1.0.2",
"aws-smithy-types 1.0.2",
"aws-credential-types 1.0.0",
"aws-smithy-async 1.0.1",
"aws-smithy-runtime-api 1.0.1",
"aws-smithy-types 1.0.1",
"http",
"rustc_version 0.4.0",
"tracing",
@@ -1770,9 +1769,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.10"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1780,9 +1779,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.9"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
dependencies = [
"anstream",
"anstyle",
@@ -2068,7 +2067,7 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"memoffset 0.9.0",
"scopeguard",
]
@@ -2853,12 +2852,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -4005,9 +4004,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.66"
version = "0.3.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
dependencies = [
"wasm-bindgen",
]
@@ -4445,6 +4444,15 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.9.0"
@@ -4655,6 +4663,19 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset 0.7.1",
"pin-utils",
]
[[package]]
name = "nix"
version = "0.27.1"
@@ -4819,7 +4840,7 @@ dependencies = [
"quick-xml 0.31.0",
"rand 0.8.5",
"reqwest",
"ring 0.17.6",
"ring 0.17.5",
"serde",
"serde_json",
"snafu",
@@ -5293,7 +5314,7 @@ checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der 0.7.8",
"pkcs8 0.10.2",
"spki 0.7.3",
"spki 0.7.2",
]
[[package]]
@@ -5313,7 +5334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der 0.7.8",
"spki 0.7.3",
"spki 0.7.2",
]
[[package]]
@@ -6431,9 +6452,9 @@ dependencies = [
[[package]]
name = "ring"
version = "0.17.6"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866"
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
dependencies = [
"cc",
"getrandom 0.2.11",
@@ -6494,9 +6515,9 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.9.5"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474"
checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f"
dependencies = [
"const-oid",
"digest 0.10.7",
@@ -6507,7 +6528,7 @@ dependencies = [
"pkcs8 0.10.2",
"rand_core 0.6.4",
"signature 2.2.0",
"spki 0.7.3",
"spki 0.7.2",
"subtle",
"zeroize",
]
@@ -6667,7 +6688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
dependencies = [
"log",
"ring 0.17.6",
"ring 0.17.5",
"rustls-webpki",
"sct",
]
@@ -6699,7 +6720,7 @@ version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring 0.17.6",
"ring 0.17.5",
"untrusted 0.9.0",
]
@@ -6877,7 +6898,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring 0.17.6",
"ring 0.17.5",
"untrusted 0.9.0",
]
@@ -7394,9 +7415,9 @@ dependencies = [
[[package]]
name = "spki"
version = "0.7.3"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
dependencies = [
"base64ct",
"der 0.7.8",
@@ -7657,7 +7678,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"rand 0.8.5",
"rsa 0.9.5",
"rsa 0.9.4",
"serde",
"sha1",
"sha2 0.10.8",
@@ -8281,9 +8302,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.29.11"
version = "0.29.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666"
checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5"
dependencies = [
"cfg-if",
"core-foundation-sys",
@@ -9371,9 +9392,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.89"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -9381,9 +9402,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.89"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
dependencies = [
"bumpalo",
"log",
@@ -9396,9 +9417,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.39"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02"
dependencies = [
"cfg-if",
"js-sys",
@@ -9408,9 +9429,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.89"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -9418,9 +9439,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.89"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
dependencies = [
"proc-macro2",
"quote",
@@ -9431,15 +9452,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.89"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.39"
version = "0.3.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403"
checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -9451,9 +9472,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.39"
version = "0.3.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89"
checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735"
dependencies = [
"proc-macro2",
"quote",
@@ -9484,9 +9505,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.66"
version = "0.3.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -9498,7 +9519,7 @@ version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring 0.17.6",
"ring 0.17.5",
"untrusted 0.9.0",
]
@@ -9572,7 +9593,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"axum",
@@ -9607,7 +9628,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"argon2",
@@ -9679,7 +9700,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"base64 0.21.5",
"chrono",
@@ -9697,7 +9718,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"chrono",
"serde",
@@ -9710,7 +9731,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"axum",
@@ -9741,7 +9762,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"serde",
"serde_json",
@@ -9749,7 +9770,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -9760,7 +9781,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"gosyn",
@@ -9772,7 +9793,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -9783,7 +9804,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"itertools 0.12.0",
@@ -9794,7 +9815,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -9811,7 +9832,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -9822,7 +9843,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -9839,7 +9860,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"getrandom 0.2.11",
@@ -9857,7 +9878,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -9889,7 +9910,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.217.0"
version = "1.216.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -9901,7 +9922,6 @@ dependencies = [
"deno_console",
"deno_core",
"deno_fetch",
"deno_tls",
"deno_url",
"deno_web",
"deno_webidl",
@@ -9916,7 +9936,7 @@ dependencies = [
"lazy_static",
"mysql_async",
"native-tls",
"nix",
"nix 0.27.1",
"once_cell",
"pem 3.0.2",
"postgres-native-tls",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.217.0"
version = "1.216.0"
authors.workspace = true
edition.workspace = true
@@ -21,7 +21,7 @@ members = [
]
[workspace.package]
version = "1.217.0"
version = "1.216.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -98,7 +98,7 @@ hyper = { version = "^0", features = ["full"] }
tokio = { version = "^1", features = ["full", "tracing"] }
tower = "^0"
tower-http = { version = "^0.4", features = ["trace", "cors"] }
tower-cookies = "0.9.0"
tower-cookies = "^0"
serde = "^1"
serde_json = { version = "^1", features = ["preserve_order", "raw_value"] }
uuid = { version = "^1", features = ["serde", "v4"] }
@@ -137,7 +137,6 @@ json-pointer = "^0"
itertools = "^0"
regex = "^1"
deno_fetch = "0.139.0"
deno_tls = "0.102.0"
deno_console = "0.115.0"
deno_url = "0.115.0"
deno_webidl = "0.115.0"

View File

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

View File

@@ -1,3 +0,0 @@
-- Add up migration script here
INSERT INTO config (name, config) VALUES
('worker__reports', '{"init_bash": "apt-get update\napt-get install -y chromium", "worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "chromium"]}') ON CONFLICT DO NOTHING;

View File

@@ -719,7 +719,7 @@ async fn handle_zombie_jobs<R: rsmq_async::RsmqConnection + Send + Sync + Clone>
&db,
&job.workspace_id,
&job.permissioned_as,
"ephemeral-script",
"ephemeral-zombie-jobs",
*SCRIPT_TOKEN_EXPIRY,
&job.email,
)

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.217.0
version: 1.216.0
title: Windmill API
contact:
@@ -1634,11 +1634,6 @@ paths:
operationId: listTokens
tags:
- user
parameters:
- name: exclude_ephemeral
in: query
schema:
type: boolean
responses:
"200":
description: truncated token
@@ -2197,8 +2192,7 @@ paths:
/w/{workspace}/resources/get_value_interpolated/{path}:
get:
summary:
get resource interpolated (variables and resources are fully unrolled)
summary: get resource interpolated (variables and resources are fully unrolled)
operationId: getResourceValueInterpolated
tags:
- resource
@@ -2886,8 +2880,7 @@ paths:
schema:
type: string
- name: first_parent_hash
description:
mask to filter scripts whom first direct parent has exact hash
description: mask to filter scripts whom first direct parent has exact hash
in: query
schema:
type: string
@@ -3082,8 +3075,7 @@ paths:
/workers/custom_tags:
get:
summary:
get all instance custom tags (tags are used to dispatch jobs to
summary: get all instance custom tags (tags are used to dispatch jobs to
different worker groups)
operationId: getCustomTags
tags:
@@ -3134,8 +3126,7 @@ paths:
/w/{workspace}/scripts/delete/h/{hash}:
post:
summary:
delete script by hash (erase content but keep hash, require admin)
summary: delete script by hash (erase content but keep hash, require admin)
operationId: deleteScriptByHash
tags:
- script
@@ -3327,16 +3318,14 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
- $ref: "#/components/parameters/ParentJob"
- $ref: "#/components/parameters/NewJobId"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4309,8 +4298,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -4318,8 +4306,7 @@ paths:
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the flow owner (default false)
description: make the run invisible to the the flow owner (default false)
in: query
schema:
type: boolean
@@ -4371,8 +4358,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -4380,8 +4366,7 @@ paths:
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the flow owner (default false)
description: make the run invisible to the the flow owner (default false)
in: query
schema:
type: boolean
@@ -4419,8 +4404,7 @@ paths:
type: string
format: date-time
- name: scheduled_in_secs
description:
schedule the script to execute in the number of seconds starting now
description: schedule the script to execute in the number of seconds starting now
in: query
schema:
type: integer
@@ -4428,8 +4412,7 @@ paths:
- $ref: "#/components/parameters/NewJobId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4460,8 +4443,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4494,8 +4476,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/IncludeHeader"
- name: invisible_to_owner
description:
make the run invisible to the the script owner (default false)
description: make the run invisible to the the script owner (default false)
in: query
schema:
type: boolean
@@ -4710,8 +4691,7 @@ paths:
- job
responses:
"200":
description:
the timestamp of the db that can be used to compute the drift
description: the timestamp of the db that can be used to compute the drift
content:
application/json:
schema:
@@ -4947,8 +4927,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
@@ -5499,8 +5478,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- name: only_member_of
in: query
description:
only list the groups the user is member of (default false)
description: only list the groups the user is member of (default false)
schema:
type: boolean
responses:
@@ -5688,8 +5666,7 @@ paths:
- $ref: "#/components/parameters/WorkspaceId"
- name: only_member_of
in: query
description:
only list the folders the user is member of (default false)
description: only list the folders the user is member of (default false)
schema:
type: boolean
responses:
@@ -6590,8 +6567,7 @@ components:
type: integer
PerPage:
name: per_page
description:
number of items to return for a given page (default 30, max 100)
description: number of items to return for a given page (default 30, max 100)
in: query
schema:
type: integer
@@ -6725,8 +6701,7 @@ components:
type: boolean
ArgsFilter:
name: args
description:
filter on jobs containing those args as a json subset (@> in postgres)
description: filter on jobs containing those args as a json subset (@> in postgres)
in: query
schema:
type: string
@@ -6738,8 +6713,7 @@ components:
type: string
ResultFilter:
name: result
description:
filter on jobs containing those result as a json subset (@> in postgres)
description: filter on jobs containing those result as a json subset (@> in postgres)
in: query
schema:
type: string

View File

@@ -102,13 +102,9 @@ async fn proxy(
&openai_resource_path,
&w_id
)
.fetch_optional(&mut *tx)
.await?
.ok_or_else(|| {
create_openai_json_error(format!(
"Could not find the OpenAI resource at path {openai_resource_path}, update the resource path in the workspace settings"
))
})?;
.fetch_one(&mut *tx)
.await?;
tx.commit().await?;
if resource.is_none() {
return Err(create_openai_json_error(

View File

@@ -2127,37 +2127,19 @@ async fn impersonate(
Ok((StatusCode::CREATED, token))
}
#[derive(Deserialize)]
struct ListTokenQuery {
exclude_ephemeral: Option<bool>,
}
async fn list_tokens(
Extension(db): Extension<DB>,
ApiAuthed { email, .. }: ApiAuthed,
Query(query): Query<ListTokenQuery>,
) -> JsonResult<Vec<TruncatedToken>> {
let rows = if query.exclude_ephemeral.unwrap_or(false) {
sqlx::query_as!(
TruncatedToken,
"SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, \
last_used_at, scopes FROM token WHERE email = $1 AND label != 'ephemeral-script'
ORDER BY created_at DESC",
email,
)
.fetch_all(&db)
.await?
} else {
sqlx::query_as!(
TruncatedToken,
"SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, \
last_used_at, scopes FROM token WHERE email = $1
ORDER BY created_at DESC",
email,
)
.fetch_all(&db)
.await?
};
let rows = sqlx::query_as!(
TruncatedToken,
"SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, \
last_used_at, scopes FROM token WHERE email = $1
ORDER BY created_at DESC",
email,
)
.fetch_all(&db)
.await?;
Ok(Json(rows))
}

View File

@@ -8,12 +8,12 @@
use windmill_common::{
error::{self, Error},
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
users::SUPERADMIN_SECRET_EMAIL,
DB,
};
pub async fn require_super_admin(db: &DB, email: &str) -> error::Result<()> {
if email == SUPERADMIN_SECRET_EMAIL || email == SUPERADMIN_NOTIFICATION_EMAIL {
if email == SUPERADMIN_SECRET_EMAIL {
return Ok(());
}
let is_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)

View File

@@ -7,7 +7,6 @@
*/
pub const SUPERADMIN_SECRET_EMAIL: &str = "superadmin_secret@windmill.dev";
pub const SUPERADMIN_NOTIFICATION_EMAIL: &str = "superadmin_notification@windmill.dev";
pub fn username_to_permissioned_as(user: &str) -> String {
if user.contains('@') {

View File

@@ -55,7 +55,7 @@ use windmill_common::{
oauth2::WORKSPACE_SLACK_BOT_TOKEN_PATH,
schedule::Schedule,
scripts::{ScriptHash, ScriptLang},
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
users::SUPERADMIN_SECRET_EMAIL,
worker::{to_raw_value, WORKER_CONFIG},
DB, METRICS_ENABLED,
};
@@ -2095,8 +2095,8 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
job_payload: JobPayload,
args: T,
user: &str,
mut email: &str,
mut permissioned_as: String,
email: &str,
permissioned_as: String,
scheduled_for_o: Option<chrono::DateTime<chrono::Utc>>,
schedule_path: Option<String>,
parent_job: Option<Uuid>,
@@ -2249,10 +2249,6 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
priority,
),
JobPayload::ScriptHub { path } => {
if path == "hub/7771/slack" {
permissioned_as = SUPERADMIN_NOTIFICATION_EMAIL.to_string();
email = SUPERADMIN_NOTIFICATION_EMAIL;
}
(
None,
Some(path),

View File

@@ -60,7 +60,6 @@ deno_console.workspace = true
deno_url.workspace = true
deno_core.workspace = true
deno_ast.workspace = true
deno_tls.workspace = true
postgres-native-tls.workspace = true
native-tls.workspace = true
mysql_async.workspace = true

View File

@@ -16,7 +16,7 @@ use crate::{
start_child_process, write_file, write_file_binary,
},
AuthedClientBackgroundTask, BUN_CACHE_DIR, BUN_PATH, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV,
NSJAIL_PATH, PATH_ENV, TZ_ENV,
NPM_CONFIG_REGISTRY, NSJAIL_PATH, PATH_ENV, TZ_ENV,
};
use tokio::{
@@ -512,7 +512,7 @@ plugin(p)
}
pub async fn get_common_bun_proc_envs(base_internal_url: &str) -> HashMap<String, String> {
let bun_envs: HashMap<String, String> = HashMap::from([
let mut bun_envs: HashMap<String, String> = HashMap::from([
(String::from("PATH"), PATH_ENV.clone()),
(String::from("HOME"), HOME_ENV.clone()),
(String::from("TZ"), TZ_ENV.clone()),
@@ -529,6 +529,10 @@ pub async fn get_common_bun_proc_envs(base_internal_url: &str) -> HashMap<String
BUN_CACHE_DIR.to_string(),
),
]);
if let Some(ref s) = NPM_CONFIG_REGISTRY.read().await.clone() {
bun_envs.insert(String::from("NPM_CONFIG_REGISTRY"), s.clone());
}
return bun_envs;
}

View File

@@ -41,8 +41,6 @@ lazy_static::lazy_static! {
.map(|x| format!(";{x}"))
.unwrap_or_else(|| String::new());
static ref DENO_CERT: String = std::env::var("DENO_CERT").ok().unwrap_or_else(|| String::new());
static ref DENO_TLS_CA_STORE: String = std::env::var("DENO_TLS_CA_STORE").ok().unwrap_or_else(|| String::new());
}
async fn get_common_deno_proc_envs(
@@ -72,12 +70,6 @@ async fn get_common_deno_proc_envs(
if let Some(ref s) = NPM_CONFIG_REGISTRY.read().await.clone() {
deno_envs.insert(String::from("NPM_CONFIG_REGISTRY"), s.clone());
}
if DENO_CERT.len() > 0 {
deno_envs.insert(String::from("DENO_CERT"), DENO_CERT.clone());
}
if DENO_TLS_CA_STORE.len() > 0 {
deno_envs.insert(String::from("DENO_TLS_CA_STORE"), DENO_TLS_CA_STORE.clone());
}
return deno_envs;
}

View File

@@ -6,17 +6,16 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, env, io::{BufReader, self}};
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
use deno_ast::{ParseParams, SourceTextInfo};
use deno_core::{
op, serde_v8,
v8::IsolateHandle,
v8::{self},
Extension, JsRuntime, Op, OpState, RuntimeOptions, Snapshot, error::AnyError,
Extension, JsRuntime, Op, OpState, RuntimeOptions, Snapshot,
};
use deno_fetch::FetchPermissions;
use deno_tls::{rustls::RootCertStore, rustls_pemfile};
use deno_web::{BlobStore, TimersPermission};
use itertools::Itertools;
use lazy_static::lazy_static;
@@ -39,33 +38,6 @@ pub struct IdContext {
pub previous_id: String,
}
pub struct ContainerRootCertStoreProvider {
root_cert_store: RootCertStore,
}
impl ContainerRootCertStoreProvider {
fn new() -> ContainerRootCertStoreProvider {
return ContainerRootCertStoreProvider {
root_cert_store: deno_tls::create_default_root_cert_store(),
}
}
fn add_certificate(&mut self, cert_path: String) -> io::Result<()> {
let cert_file = std::fs::File::open(cert_path)?;
let mut reader = BufReader::new(cert_file);
let pem_file = rustls_pemfile::certs(&mut reader)?;
self.root_cert_store.add_parsable_certificates(&pem_file);
Ok(())
}
}
impl deno_tls::RootCertStoreProvider for ContainerRootCertStoreProvider {
fn get_or_try_init(&self) -> Result<&RootCertStore, AnyError> {
Ok(&self.root_cert_store)
}
}
pub struct PermissionsContainer;
impl FetchPermissions for PermissionsContainer {
@@ -560,18 +532,6 @@ pub async fn eval_fetch_timeout(
..Default::default()
};
let deno_fetch_options = if let Some(cert_path) = env::var("DENO_CERT").ok() {
let mut cert_store_provider = ContainerRootCertStoreProvider::new();
cert_store_provider.add_certificate(cert_path)?;
deno_fetch::Options {
root_cert_store_provider: Some(Arc::new(cert_store_provider)),
..Default::default()
}
} else {
Default::default()
};
let exts: Vec<Extension> = vec![
deno_webidl::deno_webidl::init_ops(),
deno_url::deno_url::init_ops(),
@@ -580,7 +540,9 @@ pub async fn eval_fetch_timeout(
Arc::new(BlobStore::default()),
None,
),
deno_fetch::deno_fetch::init_ops::<PermissionsContainer>(deno_fetch_options),
deno_fetch::deno_fetch::init_ops::<PermissionsContainer>(
Default::default(),
),
ext
];

View File

@@ -30,7 +30,6 @@ lazy_static::lazy_static! {
static ref PIP_INDEX_URL: Option<String> = std::env::var("PIP_INDEX_URL").ok();
static ref PIP_TRUSTED_HOST: Option<String> = std::env::var("PIP_TRUSTED_HOST").ok();
static ref PIP_INDEX_CERT: Option<String> = std::env::var("PIP_INDEX_CERT").ok();
static ref RELATIVE_IMPORT_REGEX: Regex = Regex::new(r#"(import|from)\s(((u|f)\.)|\.)"#).unwrap();
@@ -122,9 +121,6 @@ pub async fn pip_compile(
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
args.extend(["--trusted-host", host]);
}
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
args.extend(["--cert", cert_path]);
}
let mut child_cmd = Command::new("pip-compile");
child_cmd
@@ -609,9 +605,6 @@ pub async fn handle_python_reqs(
if let Some(url) = PIP_INDEX_URL.as_ref() {
vars.push(("INDEX_URL", url));
}
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
vars.push(("PIP_INDEX_CERT", cert_path));
}
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
vars.push(("TRUSTED_HOST", host));
}
@@ -711,9 +704,6 @@ pub async fn handle_python_reqs(
if let Some(url) = PIP_INDEX_URL.as_ref() {
command_args.extend(["--index-url", url]);
}
if let Some(cert_path) = PIP_INDEX_CERT.as_ref() {
command_args.extend(["--cert", cert_path]);
}
if let Some(host) = PIP_TRUSTED_HOST.as_ref() {
command_args.extend(["--trusted-host", &host]);
}

View File

@@ -32,7 +32,7 @@ use windmill_common::{
flows::{FlowModule, FlowModuleValue, FlowValue},
jobs::{JobKind, QueuedJob},
scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang},
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
users::SUPERADMIN_SECRET_EMAIL,
utils::{rd_string, StripPath},
worker::{
to_raw_value, to_raw_value_owned, update_ping, CLOUD_HOSTED, WORKER_CONFIG, WORKER_GROUP,
@@ -142,8 +142,7 @@ pub async fn create_token_for_owner(
.fetch_optional(db)
.await?
.unwrap_or(false)
|| email == SUPERADMIN_SECRET_EMAIL
|| email == SUPERADMIN_NOTIFICATION_EMAIL;
|| email == SUPERADMIN_SECRET_EMAIL;
sqlx::query_scalar!(
"INSERT INTO token
@@ -958,7 +957,6 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
let worker_flow_transition_duration2 = worker_flow_transition_duration.clone();
let worker_name2 = worker_name.clone();
let killpill_tx2 = killpill_tx.clone();
let send_result = tokio::spawn(async move {
while let Some(jc) = job_completed_rx.recv().await {
if let Some(wj) = worker_job_completed_channel_queue2.as_ref() {
@@ -970,6 +968,7 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
let same_worker_tx2 = same_worker_tx2.clone();
let rsmq2 = rsmq2.clone();
let worker_name = worker_name2.clone();
if matches!(jc.job.job_kind, JobKind::Noop) || is_dedicated_worker {
thread_count.fetch_add(1, Ordering::SeqCst);
let thread_count = thread_count.clone();
@@ -993,7 +992,7 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
let worker_save_completed_job_duration2 =
worker_save_completed_job_duration.clone();
let worker_flow_transition_duration2 = worker_flow_transition_duration.clone();
let killpill_tx = killpill_tx2.clone();
tokio::spawn(async move {
#[cfg(feature = "benchmark")]
let process_start = Instant::now();
@@ -1056,7 +1055,6 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
.execute(&db2)
.await
.expect("update config to trigger restart of all dedicated workers at that config");
killpill_tx.send(()).unwrap_or_default();
}
});
} else {
@@ -1107,71 +1105,79 @@ pub async fn run_worker<R: rsmq_async::RsmqConnection + Send + Sync + Clone + 's
let is_flow_worker;
if let Some(flow_path) = _wp.path.strip_prefix("flow/") {
is_flow_worker = true;
let value = sqlx::query_scalar!(
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
flow_path,
_wp.workspace_id
)
.fetch_optional(db)
.await;
if let Ok(v) = value {
if let Some(v) = v {
let value = serde_json::from_value::<FlowValue>(v).map_err(|err| {
Error::InternalErr(format!(
"could not convert json to flow for {flow_path}: {err:?}"
))
});
if let Ok(flow) = value {
let workers = spawn_dedicated_workers_for_flow(
&flow.modules,
&_wp.path,
&_wp.workspace_id,
killpill_tx.clone(),
&killpill_rx,
db,
&worker_dir,
base_internal_url,
&worker_name,
&job_completed_tx,
)
.await;
workers.into_iter().for_each(|(path, sender, handle)| {
dedicated_handles.push(handle);
hm.insert(path, sender);
loop {
let value = sqlx::query_scalar!(
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
flow_path,
_wp.workspace_id
)
.fetch_optional(db)
.await;
if let Ok(v) = value {
if let Some(v) = v {
let value = serde_json::from_value::<FlowValue>(v).map_err(|err| {
Error::InternalErr(format!(
"could not convert json to flow for {flow_path}: {err:?}"
))
});
if let Ok(flow) = value {
let workers = spawn_dedicated_workers_for_flow(
&flow.modules,
&_wp.path,
&_wp.workspace_id,
killpill_tx.clone(),
&killpill_rx,
db,
&worker_dir,
base_internal_url,
&worker_name,
&job_completed_tx,
)
.await;
workers.into_iter().for_each(|(path, sender, handle)| {
dedicated_handles.push(handle);
hm.insert(path, sender);
});
break;
}
} else {
tracing::error!(
"flow present but value not found for {}, waiting 10s",
flow_path
);
}
} else {
tracing::error!(
"flow present but value not found for dedicated worker. {}",
flow_path
);
tracing::error!("flow not found for {}, waiting 10s,", flow_path);
}
} else {
tracing::error!("flow not found for dedicated worker: {}. Waiting for dependency job and expected to restart.", flow_path);
tokio::time::sleep(Duration::from_millis(10000)).await;
}
} else {
is_flow_worker = false;
if let Some((path, sender, handle)) = spawn_dedicated_worker(
SpawnWorker::Script { path: _wp.path.clone(), hash: None },
&_wp.workspace_id,
killpill_tx.clone(),
&killpill_rx,
db,
&worker_dir,
base_internal_url,
&worker_name,
&job_completed_tx,
None,
)
.await
{
dedicated_handles.push(handle);
hm.insert(path, sender);
} else {
tracing::error!(
"failed to spawn dedicated worker for {}, script not found",
loop {
if let Some((path, sender, handle)) = spawn_dedicated_worker(
SpawnWorker::Script { path: _wp.path.clone(), hash: None },
&_wp.workspace_id,
killpill_tx.clone(),
&killpill_rx,
db,
&worker_dir,
base_internal_url,
&worker_name,
&job_completed_tx,
None,
)
.await
{
dedicated_handles.push(handle);
hm.insert(path, sender);
break;
} else {
tracing::error!(
"failed to spawn dedicated worker for {}, script found but not in a compatible language. Retrying 10s",
_wp.path
);
tokio::time::sleep(Duration::from_millis(10000)).await;
}
}
}
(hm, is_flow_worker)
@@ -1845,7 +1851,7 @@ async fn spawn_dedicated_worker(
{
let (dedicated_worker_tx, dedicated_worker_rx) =
mpsc::channel::<Arc<QueuedJob>>(MAX_BUFFERED_DEDICATED_JOBS);
let killpill_rx = killpill_rx.resubscribe();
let mut killpill_rx = killpill_rx.resubscribe();
let db = db.clone();
let base_internal_url = base_internal_url.to_string();
let worker_name = worker_name.to_string();
@@ -1864,12 +1870,16 @@ async fn spawn_dedicated_worker(
let (content, lock, language, envs) = match sw {
SpawnWorker::Script { path, hash } => {
let q = if let Some(hash) = hash {
get_script_content_by_hash(&hash, &w_id, &db).await.map(
|r: ContentReqLangEnvs| Some((r.content, r.lockfile, r.language, r.envs)),
)
} else {
sqlx::query_as::<_, (String, Option<String>, Option<ScriptLang>, Option<Vec<String>>)>(
let r;
loop {
let q = if let Some(hash) = hash {
get_script_content_by_hash(&hash, &w_id, &db).await.map(
|r: ContentReqLangEnvs| {
Some((r.content, r.lockfile, r.language, r.envs))
},
)
} else {
sqlx::query_as::<_, (String, Option<String>, Option<ScriptLang>, Option<Vec<String>>)>(
"SELECT content, lock, language, envs FROM script WHERE path = $1 AND workspace_id = $2 AND
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2 AND
deleted = false AND lock IS not NULL AND lock_error_logs IS NULL)",
@@ -1879,23 +1889,37 @@ async fn spawn_dedicated_worker(
.fetch_optional(&db)
.await
.map_err(|e| Error::InternalErr(format!("expected content and lock: {e}")))
};
if let Ok(q) = q {
if let Some(wp) = q {
wp
};
if let Ok(q) = q {
if let Some(wp) = q {
r = wp;
break;
} else {
tracing::error!(
"Failed to fetch script `{}` in workspace {} for dedicated worker. Retrying in 10s.",
path,
w_id
);
tokio::select! {
biased;
_ = killpill_rx.recv() => {
tracing::info!("Killing dedicated worker while it was attempting to fetch script");
return None;
}
_ = tokio::time::sleep(Duration::from_secs(10)) => {
continue;
}
}
}
} else {
tracing::error!(
"Failed to fetch script `{}` in workspace {} for dedicated worker.",
path,
w_id
);
tracing::error!("Failed to fetch script for dedicated worker");
killpill_tx.send(()).expect("send");
return None;
}
} else {
tracing::error!("Failed to fetch script for dedicated worker");
killpill_tx.send(()).expect("send");
return None;
}
r
}
SpawnWorker::RawScript { content, lock, lang, .. } => (content, lock, Some(lang), None),
};

View File

@@ -1,5 +1,5 @@
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
import { JSDOM } from "https://jspm.dev/jsdom@22";
import { JSDOM } from "https://jspm.dev/jsdom";
type DataPoint = {
value: number;

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.217.0";
export const VERSION = "v1.216.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -31,7 +31,7 @@ addEventListener("error", (event) => {
}
});
export const VERSION = "v1.217.0";
export const VERSION = "v1.216.0";
let command: any = new Command()
.name("wmill")

View File

@@ -1,16 +0,0 @@
{$BASE_URL} {
bind {$ADDRESS}
tls /certs/caddy.crt /certs/caddy.key
handle_path /static/* {
root * /static/
file_server {
browse
}
}
handle_path /npm/* {
reverse_proxy * http://npm_registry:4873
}
handle_path /* {
reverse_proxy * http://pypi_server:8080
}
}

View File

@@ -1,146 +0,0 @@
Private package registry with self-signed certificates
==================================================
Setup a private NPM registry and Pypi server behind a revers proxy (Caddy) exposing an HTTPS endpoint with self signed certificates
### Setup
1. Generate self signed certificates (or bring your own)
```bash
cd certs
# feel free to read the file anc change the
./generate_certs.sh
# choose a password for the RootCA key. You'll need to input it for pretty much all following steps
```
At the end of the script, you should have multiple files in the `certs/` folder. The most important ones are:
- `windmill-root.key` (Root CA private key)
- `windmill-root.crt` (Root CA certificate)
- `caddy.key` (caddy server private key)
- `caddy.crt` (caddy registry server certificate)
2. Start the docker compose stack
```bash
docker compose up -d
```
This will start the private NPM registry, the private Pypi server and Caddy as a reverse proxy sitting in front of the two. Separately, for the purpose of proving an end to end setup, it will also start minimal Windmill stack composed of just one Windmill server/worker and the associated database.
For a more complete Windmill setup, we invite you to refer to the latest [docker compose](/docker-compose.yml) at the root of this repository to setup a more evolved Windmill stack.
For the private registries/repositories, it's using [Verdaccio](https://verdaccio.org/) as an easy-to-deploy NPM registry and [pypiserver](https://pypi.org/project/pypiserver/) for Python.
## Deno pulling package from private NPM registry
Upload the custom `helloworld_npm_package` package to the private NPM registry and use it in a Windmill script
```bash
cd helloworld_npm_package
# you need to first create a registry user
# you might need to run `npm config set strict-ssl false` if the below fails. If you do, then change it back to `true` after running the 2 commands
npm adduser --registry https://localhost/npm/
npm publish --registry https://localhost/npm/
```
Go to Windmill at `http://localhost:8000`. Create a simple Deno script:
```ts
import * as testpackage from "npm:@windmill/helloworld@0.0.1"
export async function main() {
console.log(testpackage.sayHello("Windmill"))
}
```
and execute it. It should return successfully with:
```
Hello Windmill
```
Note: Native fetches to HTTPS endpoints with self signed certificates also takes into account the DENO_CERT environment variable
Go to Windmill and create a new script of type "REST" with the following content:
```ts
export async function main() {
const res = await fetch("https://caddy/static/helloworld.json", {
headers: { "Content-Type": "application/json" },
});
return res.json();
}
```
It will fetch a static json file from Caddy exposed behind its HTTPS endpoint with self signed certificate.
## Python pulling package from private NPM registry
Upload the custom `helloworld_python_module` python module to the private Pypi server
```bash
cd helloworld_python_module
python setup.py sdist
# using twine to upload the module here, get it with `pip install twine`
twine upload --repository-url https://localhost/ dist/* --cert ../certs/windmill-root.crt
# no username and password, just press enter. For the purpose of the demo we're running pypiserver completely unauthenticated
```
You can check that the package is uploaded by visiting [https://localhost/simple](https://localhost/simple).
Go to Windmill at `http://localhost:8000`. Create a simple Python script:
```python
#requirements:
#windmill-helloworld==0.0.1
import windmill_helloworld
def main():
print(windmill_helloworld.say_hello("Windmill"))
```
and execute it. It should return successfully with:
```
Hello Windmill
```
### MISC
1. `DENO_TLS_CA_STORE` VS `DENO_CERT`
Both works. `DENO_CERT` is better b/c you just have to set it to the path of the trusted Root CA certificate, and deno will trust this certificate.
When using `DENO_TLS_CA_STORE=system`, you _have to_ make the server trust the custom Root CA certificate with the following commands:
```bash
# in the windmill-server container:
cp /custom-certs/windmill-root.crt /usr/local/share/ca-certificates/
update-ca-certificates # as root
# the output should tell (among other things): " ... 1 added, 0 removed; done. ..."
```
2. Running Deno scripts manually in the Windmill container
This could be useful for debugging purposes.
```bash
# log into the Windmill Server container
docker exec -it <WINDMILL_SERVER_CONTAINER> /bin/bash
# Go into Deno REPL
deno
# try to import the package
import * as testpackage from "npm:@windmill/helloworld@0.0.1"
> undefined
# if the above returns undefined, there's a good chance it's working. If you want to double check:
testpackage.sayHello("Windmill")
> Hello Windmill
# potentially inspect the env var available to DENO. Is you used DENO_CERT in the docker compose, check its value:
console.log(Deno.env.get("DENO_CERT"))
> /custom-certs/windmill-root.crt
```
3. Manually testing Pypi private server integration
Can be useful to debug as well:
```bash
# install the package
pip3 install --cert ../certs/windmill-root.crt -i https://localhost/simple/ windmill-helloworld
# go to python CLI and try to import it and use it
python
>>> import windmill_helloworld
>>> windmill_helloworld.say_hello("world")
'Hello world'
```

View File

@@ -1,5 +0,0 @@
*.crt
*.csr
*.key
*.ext
*.srl

View File

@@ -1,35 +0,0 @@
#!/bin/bash
set eou -pipefail
CANAME='windmill-root'
COUNTRY='FR'
STATE='Paris'
CITY='Paris'
ORGANIZATION='WindmillLabs'
ROOT_CA_CN='WindmillRootCA'
SERVER_CA_CN='caddy' # IMPORTANT: set this to the FQDN of the caddy server. Here in the docker compose stack, it will be caddy
echo "Generating RootCA key"
openssl genrsa -aes256 -out $CANAME.key 4096
echo "Generating RootCA certificate"
openssl req -x509 -new -nodes -key $CANAME.key -sha256 -days 1826 -out $CANAME.crt -subj "/CN=${ROOT_CA_CN}/C=${COUNTRY}/ST=${STATE}/L=${CITY}/O=${ORGANIZATION}"
CERTNAME=caddy
echo "Generating server certificate private key and cert signing request"
openssl req -new -nodes -out $CERTNAME.csr -newkey rsa:4096 -keyout $CERTNAME.key -subj "/CN=${SERVER_CA_CN}/C=${COUNTRY}/ST=${STATE}/L=${CITY}/O=${ORGANIZATION}"
echo "Generating server certificate"
cat > $CERTNAME.v3.ext << EOF
[v3_req]
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = caddy
DNS.2 = localhost
EOF
# ^ HERE ^ in the above alt_names, feel free to add any alternate CN
openssl x509 -req -in $CERTNAME.csr -CA $CANAME.crt -CAkey $CANAME.key -CAcreateserial -out $CERTNAME.crt -days 1826 -sha256 -extensions v3_req -extfile $CERTNAME.v3.ext

View File

@@ -1,69 +0,0 @@
version: "3.7"
services:
npm_registry:
image: verdaccio/verdaccio
environment:
- VERDACCIO_PROTOCOL=http
- VERDACCIO_PUBLIC_URL=http://caddy/npm/
volumes:
- ./verdaccio_conf:/verdaccio/conf
- npm_registry_data:/verdaccio/storage
pypi_server:
image: pypiserver/pypiserver:latest
platform: linux/x86_64
volumes:
- pypi_data:/data/packages
command: run -P . -a . /data/packages
caddy:
image: caddy:2.5.2-alpine
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./certs:/certs
- ./helloworld_static:/static/
ports:
- 443:443
environment:
- BASE_URL=":443"
db:
image: postgres:14
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: changeme
POSTGRES_DB: windmill
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
windmill_server:
image: ghcr.io/windmill-labs/windmill:main
ports:
- 8000:8000
environment:
- DATABASE_URL=postgres://postgres:changeme@db/windmill?sslmode=disable
- WORKER_TAGS=deno,go,python3,bash,flow,hub,dependency,nativets
# For DENO and REST scripts:
- NPM_CONFIG_REGISTRY=http://caddy/npm/
- DENO_CERT=/custom-certs/windmill-root.crt # this will make deno trust this RootCA for all sessions
# - DENO_TLS_CA_STORE=system # alternatively, you can use this but you'll need to manually trust the RootCA at the host level, see README.md
# For Python scripts:
- PIP_INDEX_URL=https://caddy/simple/
- PIP_INDEX_CERT=/custom-certs/windmill-root.crt # this will make pip trust this RootCA for all sessions
volumes:
- ./certs:/custom-certs
depends_on:
db:
condition: service_healthy
volumes:
npm_registry_data: null
pypi_data: null
db_data: null

View File

@@ -1 +0,0 @@
node_modules/

View File

@@ -1,4 +0,0 @@
Hello world test package
========================
Dummy NPM package to test imports from a private NPM registry in Windmill

View File

@@ -1,3 +0,0 @@
exports.sayHello = function(x) {
return `Hello ${x}`
}

View File

@@ -1,10 +0,0 @@
{
"name": "@windmill/helloworld",
"version": "0.0.1",
"description": "hello world test package",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "Windmill",
"license": "Apache-2.0"
}

View File

@@ -1,3 +0,0 @@
dist/
__pycache__/
*.egg-info/

View File

@@ -1,4 +0,0 @@
Hello world test module
=======================
Dummy Python module to test imports from a private Pypi server in Windmill

View File

@@ -1,30 +0,0 @@
import os
import setuptools
module_path = os.path.join(os.path.dirname(__file__), "windmill_helloworld.py")
setuptools.setup(
name="windmill-helloworld",
version="0.0.1",
url="https://github.com/windmill-labs/windmill/blob//examples/deploy/private-package-registry-tls/README.md",
author="WindmillLabs",
author_email="contact@windmill.dev",
description="Simple hello world python module to host on a private Pypi server",
long_description=open("README.md").read(),
py_modules=["windmill_helloworld"],
zip_safe=False,
platforms="any",
install_requires=[],
classifiers=[
"Development Status :: 2 - Pre-Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
],
)

View File

@@ -1,2 +0,0 @@
def say_hello(x: str) -> str:
return "Hello {}".format(x)

View File

@@ -1,3 +0,0 @@
{
"hello": "world"
}

View File

@@ -1,201 +0,0 @@
#
# This is the default configuration file. It allows all users to do anything,
# please read carefully the documentation and best practices to
# improve security.
#
# Do not configure host and port under `listen` in this file
# as it will be ignored when using docker.
# see https://verdaccio.org/docs/en/docker#docker-and-custom-port-configuration
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/5.x/conf
#
# Read about the best practices
# https://verdaccio.org/docs/best
# path to a directory with all packages
storage: /verdaccio/storage/data
# path to a directory with plugins to include
plugins: /verdaccio/plugins
# https://verdaccio.org/docs/webui
web:
title: Verdaccio
# comment out to disable gravatar support
# gravatar: false
# by default packages are ordercer ascendant (asc|desc)
# sort_packages: asc
# convert your UI to the dark side
# darkMode: true
# html_cache: true
# by default all features are displayed
# login: true
# showInfo: true
# showSettings: true
# In combination with darkMode you can force specific theme
# showThemeSwitch: true
# showFooter: true
# showSearch: true
# showRaw: true
# showDownloadTarball: true
# HTML tags injected after manifest <scripts/>
# scriptsBodyAfter:
# - '<script type="text/javascript" src="https://my.company.com/customJS.min.js"></script>'
# HTML tags injected before ends </head>
# metaScripts:
# - '<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>'
# - '<script type="text/javascript" src="https://browser.sentry-cdn.com/5.15.5/bundle.min.js"></script>'
# - '<meta name="robots" content="noindex" />'
# HTML tags injected first child at <body/>
# bodyBefore:
# - '<div id="myId">html before webpack scripts</div>'
# Public path for template manifest scripts (only manifest)
# publicPath: http://somedomain.org/
# https://verdaccio.org/docs/configuration#authentication
auth:
htpasswd:
file: /verdaccio/storage/htpasswd
# Maximum amount of users allowed to register, defaults to "+infinity".
# You can set this to -1 to disable registration.
# max_users: 1000
# Hash algorithm, possible options are: "bcrypt", "md5", "sha1", "crypt".
# algorithm: bcrypt # by default is crypt, but is recommended use bcrypt for new installations
# Rounds number for "bcrypt", will be ignored for other algorithms.
# rounds: 10
# https://verdaccio.org/docs/configuration#uplinks
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
# Learn how to protect your packages
# https://verdaccio.org/docs/protect-your-dependencies/
# https://verdaccio.org/docs/configuration#packages
packages:
'@*/*':
# scoped packages
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs
'**':
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all
# allow all known users to publish/publish packages
# (anyone can register by default, remember?)
publish: $authenticated
unpublish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
# To improve your security configuration and avoid dependency confusion
# consider removing the proxy property for private packages
# https://verdaccio.org/docs/best#remove-proxy-to-increase-security-at-private-packages
# https://verdaccio.org/docs/configuration#server
# You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections.
# A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
# WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough.
server:
keepAliveTimeout: 60
# Allow `req.ip` to resolve properly when Verdaccio is behind a proxy or load-balancer
# See: https://expressjs.com/en/guide/behind-proxies.html
# trustProxy: '127.0.0.1'
# https://verdaccio.org/docs/configuration#offline-publish
# publish:
# allow_offline: false
# https://verdaccio.org/docs/configuration#url-prefix
# url_prefix: /verdaccio/
# VERDACCIO_PUBLIC_URL='https://somedomain.org';
# url_prefix: '/my_prefix'
# // url -> https://somedomain.org/my_prefix/
# VERDACCIO_PUBLIC_URL='https://somedomain.org';
# url_prefix: '/'
# // url -> https://somedomain.org/
# VERDACCIO_PUBLIC_URL='https://somedomain.org/first_prefix';
# url_prefix: '/second_prefix'
# // url -> https://somedomain.org/second_prefix/'
# https://verdaccio.org/docs/configuration#security
# security:
# api:
# legacy: true
# jwt:
# sign:
# expiresIn: 29d
# verify:
# someProp: [value]
# web:
# sign:
# expiresIn: 1h # 1 hour by default
# verify:
# someProp: [value]
# https://verdaccio.org/docs/configuration#user-rate-limit
# userRateLimit:
# windowMs: 50000
# max: 1000
# https://verdaccio.org/docs/configuration#max-body-size
# max_body_size: 10mb
# https://verdaccio.org/docs/configuration#listen-port
# listen:
# - localhost:4873 # default value
# - http://localhost:4873 # same thing
# - 0.0.0.0:4873 # listen on all addresses (INADDR_ANY)
# - https://example.org:4873 # if you want to use https
# - "[::1]:4873" # ipv6
# - unix:/tmp/verdaccio.sock # unix socket
# The HTTPS configuration is useful if you do not consider use a HTTP Proxy
# https://verdaccio.org/docs/configuration#https
# https:
# key: ./path/verdaccio-key.pem
# cert: ./path/verdaccio-cert.pem
# ca: ./path/verdaccio-csr.pem
# https://verdaccio.org/docs/configuration#proxy
# http_proxy: http://something.local/
# https_proxy: https://something.local/
# https://verdaccio.org/docs/configuration#notifications
# notify:
# method: POST
# headers: [{ "Content-Type": "application/json" }]
# endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
# content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'
middlewares:
audit:
enabled: true
# https://verdaccio.org/docs/logger
# log settings
log: { type: stdout, format: pretty, level: http }
#experiments:
# # support for npm token command
# token: false
# # enable tarball URL redirect for hosting tarball with a different server, the tarball_url_redirect can be a template string
# tarball_url_redirect: 'https://mycdn.com/verdaccio/${packageName}/${filename}'
# # the tarball_url_redirect can be a function, takes packageName and filename and returns the url, when working with a js configuration file
# tarball_url_redirect(packageName, filename) {
# const signedUrl = // generate a signed url
# return signedUrl;
# }
# translate your registry, api i18n not available yet
# i18n:
# list of the available translations https://github.com/verdaccio/verdaccio/blob/master/packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md
# web: en-US

View File

@@ -1,20 +0,0 @@
storage: /verdaccio/storage/data
plugins: /verdaccio/plugins
auth:
htpasswd:
file: /verdaccio/storage/htpasswd
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
"@*/*":
access: $all
publish: $authenticated
"**":
access: $all
proxy: npmjs
listen:
- http://localhost:4873
- http://npm_registry:4873
url_prefix: /npm/
log: { type: stdout, format: pretty, level: http }

View File

@@ -1,24 +0,0 @@
import fs from 'fs'
import tailwindClasses from './tailwindClasses.js'
function filterTailwindClasses(classes) {
const filters = [
{ pattern: /^m(\w?)-.*$/ },
{ pattern: /^p(\w?)-.*$/ },
{ pattern: /^rounded-.*$/ },
{ pattern: /^shadow-.*$/ },
{ pattern: /^text-[^/]*$/ },
{ pattern: /^bg-[^/]*$/ },
{ pattern: /^border-[^/]*$/ },
{ pattern: /^ring-[^/]*$/ }
]
return classes.filter((className) => {
return filters.some((filter) => filter.pattern.test(className))
})
}
const filteredClasses = filterTailwindClasses(tailwindClasses)
const output = `export const filteredTailwindClasses = ${JSON.stringify(filteredClasses, null, 2)};`
fs.writeFileSync('filteredTailwindClasses.ts', output)

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.217.0",
"version": "1.216.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.217.0",
"version": "1.216.0",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",
@@ -30,7 +30,7 @@
"graphql": "^16.7.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.293.0",
"lucide-svelte": "^0.277.0",
"monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.83.5 <1.84.0",
"monaco-graphql": "^1.3.0",
"monaco-languageclient": "~7.0.1",
@@ -4982,9 +4982,9 @@
"optional": true
},
"node_modules/lucide-svelte": {
"version": "0.293.0",
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.293.0.tgz",
"integrity": "sha512-nQ6QO6anjjiVoipw3dNptzI6NWuiArlhysVtRpoAWICSQVMR3ybwVf6piUUqbjGe+RfGufLzCl93C/yhsS+p4w==",
"version": "0.277.0",
"resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.277.0.tgz",
"integrity": "sha512-HrlMk0mTOd3MVSWA2ZHR79QrCycLRV4pey4HWGnGGoVFsh0/P7zGyDCJxy/MKe2t4SJtcStLdKIybMHc5pjFSQ==",
"peerDependencies": {
"svelte": ">=3 <5"
}

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.217.0",
"version": "1.216.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -14,7 +14,7 @@
"generate-backend-client-mac": "openapi --input ../backend/windmill-api/openapi.yaml --output ./src/lib/gen --useOptions",
"pretest": "tsc --incremental -p tests/tsconfig.json",
"test": "playwright test --config=tests-out/playwright.config.js",
"filter-classes": "node filterTailwindClasses.js"
"delele-orphan-components": "sh scripts/delete-orphan-components.sh"
},
"devDependencies": {
"@floating-ui/core": "^1.3.1",
@@ -113,7 +113,7 @@
"graphql": "^16.7.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.293.0",
"lucide-svelte": "^0.277.0",
"monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.83.5 <1.84.0",
"monaco-graphql": "^1.3.0",
"monaco-languageclient": "~7.0.1",
@@ -402,4 +402,4 @@
"optionalDependencies": {
"fsevents": "^2.3.3"
}
}
}

View File

@@ -0,0 +1,72 @@
#!/bin/bash
# ANSI color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# get the list of all .svelte files
svelte_files=$(find src -type f -name "*.svelte")
# Array to hold unused files
declare -a unused_files
echo -e "${NC}Scanning src folder to find all .svelte files"
echo -e " ${GREEN}.${NC} means the .svelte is imported in another file"
echo -e " ${RED}x${NC} means the .svelte is not imported and should likely be removed"
# loop over each svelte file
for svelte_file in $svelte_files
do
# extract the filename
filename=$(basename -- "$svelte_file")
# skip files starting with '+'
if [[ "$filename" == +* ]]
then
echo -n -e "${GREEN}.${NC}"
continue
fi
# search for the filename in all files
found=$(grep -rl "$filename" src)
# if nothing was found, then the file is unused
if [[ -z $found ]]
then
echo -n -e "${RED}x${NC}"
unused_files+=("$svelte_file")
else
echo -n -e "${GREEN}.${NC}"
fi
done
# Print a newline after progress dots
echo
echo
# Print unused files
for file in "${unused_files[@]}"
do
echo -e "${RED}Unused Svelte file: $file${NC}"
done
# If no unused components found, print the message and exit
if [ ${#unused_files[@]} -eq 0 ]; then
echo -e "${GREEN}No unused components found.${NC}"
exit 0
fi
# Delete files if user confirms
if [ ${#unused_files[@]} -gt 0 ]; then
echo -e -n "${GREEN}Do you want to delete these ${#unused_files[@]} files? (y/n) ${NC}"
read answer
if [ "$answer" != "${answer#[Yy]}" ] ;then
for file in "${unused_files[@]}"
do
rm "$file"
echo -e "${RED}Deleted $file${NC}"
done
fi
fi

View File

@@ -301,17 +301,11 @@
} gap-2 h-full items-center`}
>
{#if file_info.type === 'folder'}
{#if file_info.collapsed}<FolderClosed size={16} />{:else}<FolderOpen
size={16}
/>{/if}
<div class="truncate text-ellipsis w-56">
{file_info.display_name}
</div>
{#if file_info.collapsed}<FolderClosed />{:else}<FolderOpen />{/if}
{file_info.display_name}
{:else}
<File size={16} />
<div class="truncate text-ellipsis w-56">
{file_info.display_name}
</div>
<File />
{file_info.display_name}
{/if}
</div>
</div>
@@ -372,7 +366,7 @@
{/if}
</div>
<pre class="grow whitespace-no-wrap break-words"
>{#if !emptyString(filePreview.contentPreview)}{filePreview.contentPreview}{:else if filePreview.contentType !== undefined}Preview impossible.{/if}</pre
>{#if !emptyString(filePreview.contentPreview)}{filePreview.contentPreview}{:else if filePreview.contentType !== undefined}Preview impossible. If it's a CSV file, you can try changing the separator{/if}</pre
>
{:else if filePreviewLoading}
<div class="flex h-6 items-center text-tertiary mb-4">

View File

@@ -1,8 +1,3 @@
<script context="module">
let cssClassesLoaded = writable(false)
let tailwindClassesLoaded = writable(false)
</script>
<script lang="ts">
import { BROWSER } from 'esm-env'
@@ -25,7 +20,7 @@
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
import 'monaco-editor/esm/vs/language/css/monaco.contribution'
import { allClasses } from './apps/editor/componentsPanel/cssUtils'
import { allClasses, authorizedClassnames } from './apps/editor/componentsPanel/cssUtils'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
@@ -33,8 +28,6 @@
import { buildWorkerDefinition } from './build_workers'
import { initializeVscode } from './vscode'
import EditorTheme from './EditorTheme.svelte'
import { tailwindClasses } from './apps/editor/componentsPanel/tailwindUtils'
import { writable } from 'svelte/store'
// import { createConfiguredEditor } from 'vscode/monaco'
// import type { IStandaloneCodeEditor } from 'vscode/vscode/vs/editor/standalone/browser/standaloneCodeEditor'
@@ -123,7 +116,6 @@
let width = 0
let initialized = false
async function loadMonaco() {
await initializeVscode()
initialized = true
@@ -210,19 +202,10 @@
editor.onDidBlurEditorText(() => {
code = getCode()
})
if (lang === 'css' && !$cssClassesLoaded) {
$cssClassesLoaded = true
addCSSClassCompletions()
}
if (lang === 'tailwindcss' && !$tailwindClassesLoaded) {
languages.register({ id: 'tailwindcss' })
$tailwindClassesLoaded = true
addTailwindClassCompletions()
}
}
$: lang == 'css' && initialized && addCSSClassCompletions()
function addCSSClassCompletions() {
languages.registerCompletionItemProvider('css', {
provideCompletionItems: function (model, position, context, token) {
@@ -255,38 +238,6 @@
})
}
function addTailwindClassCompletions() {
languages.registerCompletionItemProvider('tailwindcss', {
provideCompletionItems: function (model, position, context, token) {
const word = model.getWordUntilPosition(position)
const range = {
startLineNumber: position.lineNumber,
startColumn: word.startColumn,
endLineNumber: position.lineNumber,
endColumn: word.endColumn
}
if (word && word.word) {
const currentWord = word.word
const suggestions = tailwindClasses
.filter((className) => className.includes(currentWord))
.map((className) => ({
label: className,
kind: languages.CompletionItemKind.Class,
insertText: className,
documentation: 'Custom CSS class',
range: range
}))
return { suggestions }
}
return { suggestions: [] }
}
})
}
function loadExtraLib() {
if (lang == 'javascript') {
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
@@ -301,6 +252,38 @@
} else {
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
}
} else if (lang === 'css') {
const cssClasses = authorizedClassnames.map((className) => '.' + className)
languages.registerCompletionItemProvider('css', {
provideCompletionItems: function (model, position, context, token) {
const word = model.getWordUntilPosition(position)
const range = {
startLineNumber: position.lineNumber,
startColumn: word.startColumn,
endLineNumber: position.lineNumber,
endColumn: word.endColumn
}
if (word && word.word) {
const currentWord = word.word
const suggestions = cssClasses
.filter((className) => className.includes(currentWord))
.map((className) => ({
label: className,
kind: languages.CompletionItemKind.Class,
insertText: className,
documentation: 'Custom CSS class',
range: range
}))
return { suggestions }
}
return { suggestions: [] }
}
})
}
}

View File

@@ -76,7 +76,7 @@
}
async function listTokens(): Promise<void> {
tokens = await UserService.listTokens({ excludeEphemeral: true })
tokens = await UserService.listTokens()
}
async function deleteToken(tokenPrefix: string) {

View File

@@ -2,14 +2,13 @@
import { getContext } from 'svelte'
import { initConfig, initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { getImageDataURL, initCss } from '../../utils'
import { initCss } from '../../utils'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import { ArrowDown } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import { loadIcon } from '../icon'
import Loader from '../helpers/Loader.svelte'
export let id: string
export let configuration: RichConfigurations
@@ -29,15 +28,13 @@
let iconComponent: any
$: isIcon && resolvedConfig?.media?.configuration?.icon.icon && handleIcon()
$: resolvedConfig.icon && handleIcon()
async function handleIcon() {
if (resolvedConfig?.media?.configuration?.icon.icon) {
iconComponent = await loadIcon(resolvedConfig.media.configuration.icon.icon)
if (resolvedConfig.icon) {
iconComponent = await loadIcon(resolvedConfig.icon)
}
}
$: isIcon = resolvedConfig.media?.selected == 'icon'
</script>
{#each Object.keys(components['statcomponent'].initialData.configuration) as key (key)}
@@ -60,60 +57,16 @@
{/each}
{#if render}
<div
class={twMerge(
'flex flex-row gap-4 items-center p-4 rounded-md shadow-md h-full',
css?.container?.class,
'wm-statistic-card-container'
)}
style={css?.container?.style}
>
<div
class={twMerge(
'flex items-center justify-center w-12 h-12 border rounded-md p-2 text-black',
css?.media?.class,
'wm-statistic-card-media'
)}
style={css?.media?.style}
>
{#if isIcon}
{#if resolvedConfig.media && iconComponent}
<svelte:component this={iconComponent} size={24} />
{/if}
{:else}
<Loader loading={resolvedConfig.media.configuration.image.source == undefined}>
<img
on:pointerdown|preventDefault
src={getImageDataURL(
resolvedConfig?.media?.configuration?.image?.sourceKind,
resolvedConfig?.media?.configuration?.image?.source
)}
alt={resolvedConfig.title}
/>
</Loader>
{/if}
</div>
<div class="w-full">
<div
class={twMerge(
'font-normal text-primary leading-none',
css?.title?.class,
'wm-statistic-card-title'
)}
style={css?.title?.style}
>
{resolvedConfig.title}
<div class="p-6 border rounded-md h-full flex flex-row gap-6 w-full items-center">
{#if resolvedConfig.icon && iconComponent}
<div class="bg-blue-500 rounded-md p-2">
<svelte:component this={iconComponent} size={24} color="white" />
</div>
{/if}
<div class="w-full">
<div class="text-base font-normal text-primary">{resolvedConfig.title}</div>
<div class="mt-1 flex items-baseline justify-between">
<div
class={twMerge(
'flex items-baseline text-2xl leading-none font-semibold text-blue-600 dark:text-blue-200',
css?.value?.class,
'wm-statistic-card-value'
)}
style={css?.value?.style}
>
<div class="flex items-baseline text-2xl font-semibold text-blue-600 dark:text-blue-200">
{resolvedConfig?.value}
</div>

View File

@@ -285,7 +285,7 @@
}
}
$: $table && updateTable(resolvedConfig, searchValue)
$: updateTable(resolvedConfig, searchValue)
</script>
{#each Object.keys(components['tablecomponent'].initialData.configuration) as key (key)}

View File

@@ -39,8 +39,7 @@
MoreVertical,
RefreshCw,
Save,
Smartphone,
FileClock
Smartphone
} from 'lucide-svelte'
import { getContext } from 'svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
@@ -82,7 +81,6 @@
import AppTimeline from './AppTimeline.svelte'
import type DiffDrawer from '$lib/components/DiffDrawer.svelte'
import { cloneDeep } from 'lodash'
import AppReportsDrawer from './AppReportsDrawer.svelte'
async function hash(message) {
try {
@@ -513,13 +511,7 @@
inputsDrawerOpen = true
}
},
{
displayName: 'Schedule Reports',
icon: FileClock,
action: () => {
appReportingDrawerOpen = true
}
},
...(savedApp
? [
{
@@ -554,8 +546,6 @@
}
let rightColumnSelect: 'timeline' | 'detail' = 'timeline'
let appReportingDrawerOpen = false
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -1022,8 +1012,6 @@
</DrawerContent>
</Drawer>
<AppReportsDrawer bind:open={appReportingDrawerOpen} {appPath} />
<div
class="border-b flex flex-row justify-between py-1 gap-2 gap-y-2 px-2 items-center overflow-y-visible overflow-x-auto"
>

View File

@@ -170,7 +170,6 @@
class="{$$props.class} {lockedClasses} {width} h-full bg-surface {app.fullscreen
? ''
: 'max-w-7xl'} mx-auto"
id="app-content"
>
{#if $appStore.grid}
<div

View File

@@ -1,532 +0,0 @@
<script lang="ts">
import { enterpriseLicense } from '$lib/stores'
import CronInput from '$lib/components/CronInput.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import Section from '$lib/components/Section.svelte'
import Alert from '$lib/components/common/alert/Alert.svelte'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import {
FlowService,
RawScript,
ScheduleService,
SettingService,
WorkspaceService
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptyString, formatCron, sendUserToast } from '$lib/utils'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { RotateCw, Save } from 'lucide-svelte'
import { CUSTOM_TAGS_SETTING, WORKSPACE_SLACK_BOT_TOKEN_PATH } from '$lib/consts'
import { loadSchemaFromPath } from '$lib/infer'
export let appPath: string
export let open = false
let appReportingEnabled = false
let appReportingStartupDuration = 5
let appReportingSchedule: {
cron: string
timezone: string
} = {
cron: '0 0 12 * *',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
}
let selectedTab: 'email' | 'slack' | 'discord' | 'custom' = 'custom'
let customPath: string | undefined = undefined
let customPathSchema: Record<string, any> = {}
let args: Record<string, any> = {}
let areArgsValid = true
$: customPath
? loadSchemaFromPath(customPath).then((schema) => {
customPathSchema = schema
? {
...schema,
properties: Object.fromEntries(
Object.entries(schema.properties ?? {}).filter(
([key, _]) => key !== 'pdf' && key !== 'app_path'
)
)
}
: {}
})
: (customPathSchema = {})
let isSlackConnectedWorkspace = false
async function getWorspaceSlackSetting() {
const settings = await WorkspaceService.getSettings({
workspace: $workspaceStore!
})
if (settings.slack_name) {
isSlackConnectedWorkspace = true
} else {
isSlackConnectedWorkspace = false
}
}
getWorspaceSlackSetting()
async function getAppReportingInfo() {
const flowPath = appPath + '_reports'
try {
const flow = await FlowService.getFlowByPath({
workspace: $workspaceStore!,
path: flowPath
})
const schedule = await ScheduleService.getSchedule({
workspace: $workspaceStore!,
path: flowPath
})
appReportingSchedule = {
cron: schedule.schedule,
timezone: schedule.timezone
}
appReportingStartupDuration ??= schedule.args?.startup_duration
args = schedule.args
? Object.fromEntries(
Object.entries(schedule.args).filter(
([key, _]) => key !== 'app_path' && key !== 'startup_duration'
)
)
: {}
selectedTab =
flow.value.modules[1]?.value.type === 'script'
? flow.value.modules[1].value.path === notificationScripts.email.path
? 'email'
: flow.value.modules[1].value.path === notificationScripts.slack.path
? 'slack'
: flow.value.modules[1].value.path === notificationScripts.discord.path
? 'discord'
: 'custom'
: 'custom'
customPath =
selectedTab === 'custom' &&
flow.value.modules[1]?.value.type === 'script' &&
!flow.value.modules[1].value.path.startsWith('hub/')
? flow.value.modules[1].value.path
: undefined
appReportingEnabled = true
} catch (err) {}
}
$: appPath && getAppReportingInfo()
async function disableAppReporting(skipToast = false) {
const flowPath = appPath + '_reports'
await ScheduleService.deleteSchedule({
workspace: $workspaceStore!,
path: flowPath
})
await FlowService.deleteFlowByPath({
workspace: $workspaceStore!,
path: flowPath
})
if (!skipToast) {
appReportingEnabled = false
sendUserToast('App reporting disabled')
}
}
const pdfPreviewScript = `import puppeteer from \'puppeteer-core\';
export async function main(app_path: string, startup_duration = 5) {
const browser = await puppeteer.launch({ headless: \'new\', executablePath: \'/usr/bin/chromium\', args: [\'--no-sandbox\'] });
const page = await browser.newPage();
await page.setCookie({
"name": "token",
"value": Bun.env["WM_TOKEN"],
"domain": Bun.env["WM_BASE_URL"]?.replace(/https?:\\/\\//, \'\')
})
await page.goto(Bun.env["WM_BASE_URL"] + \'/apps/get/\' + app_path + \'?workspace=\' + Bun.env["WM_WORKSPACE"]);
await new Promise((resolve, _) => {
setTimeout(resolve, startup_duration * 1000)
})
await page.$eval("#sidebar", el => el.remove())
await page.$eval("#content", el => el.classList.remove("md:pl-12"))
await page.$eval("#app-edit-btn", el => el.remove())
const elem = await page.$(\'#app-content\');
const { height } = await elem.boundingBox();
await page.setViewport({ width: 1200, height });
await new Promise((resolve, _) => {
setTimeout(resolve, 200)
})
const pdf = await page.pdf({
printBackground: true,
width: 1200,
height
});
await browser.close();
return Buffer.from(pdf).toString(\'base64\');
}`
const notificationScripts = {
discord: {
path: 'hub/7772/discord',
schema: {
type: 'object',
properties: {
discord_webhook: {
type: 'object',
format: 'resource-discord_webhook',
properties: {},
required: [],
description: ''
}
},
required: ['discord_webhook']
}
},
slack: {
path: 'hub/7771/slack', // if to be updated, also update it in in backend/windmill-queue/src/jobs.rs
schema: {
type: 'object',
properties: {
channel: {
type: 'string',
default: ''
}
},
required: ['channel']
}
},
email: {
path: 'hub/7774/smtp',
schema: {
type: 'object',
properties: {
smtp: {
type: 'object',
format: 'resource-smtp',
properties: {},
required: [],
description: ''
},
from_email: {
type: 'string',
default: ''
},
to_email: {
type: 'string',
default: ''
}
},
required: ['smtp', 'from_email', 'to_email']
}
}
}
async function updateAppReporting() {
await disableAppReporting(true)
await enableAppReporting(true)
sendUserToast('App reporting updated')
}
async function enableAppReporting(skipToast = false) {
const flowPath = appPath + '_reports'
try {
// will only work if the user is super admin
const customTags = ((await SettingService.getGlobal({
key: CUSTOM_TAGS_SETTING
})) ?? []) as string[]
if (!customTags.includes('chromium')) {
await SettingService.setGlobal({
key: CUSTOM_TAGS_SETTING,
requestBody: {
value: [...customTags, 'chromium']
}
})
}
} catch (err) {}
const inputTransforms: {
[key: string]: {
expr: string
type: 'javascript'
}
} = {
app_path: {
type: 'javascript',
expr: 'flow_input.app_path'
},
pdf: {
type: 'javascript',
expr: 'results.a'
},
...Object.fromEntries(
Object.keys(args).map((key) => [
key,
{
type: 'javascript',
expr: `flow_input.${key}`
}
])
),
...(selectedTab === 'slack'
? {
slack: {
type: 'javascript',
expr: 'flow_input.slack'
}
}
: {})
}
await FlowService.createFlow({
workspace: $workspaceStore!,
requestBody: {
summary: appPath + ' - Reports flow',
value: {
modules: [
{
id: 'a',
value: {
type: 'rawscript',
tag: 'chromium',
content: pdfPreviewScript,
language: RawScript.language.BUN,
input_transforms: {
app_path: {
expr: 'flow_input.app_path',
type: 'javascript'
},
startup_duration: {
expr: 'flow_input.startup_duration',
type: 'javascript'
}
}
}
},
{
id: 'b',
value: {
type: 'script',
path:
selectedTab === 'custom'
? customPath || ''
: notificationScripts[selectedTab].path,
input_transforms: inputTransforms
}
}
]
},
schema: {
$schema: 'https://json-schema.org/draft/2020-12/schema',
properties: {
app_path: {
description: '',
type: 'string',
default: null,
format: ''
},
startup_duration: {
description: '',
type: 'integer',
default: 5,
format: ''
},
...(selectedTab === 'custom'
? customPathSchema.properties
: notificationScripts[selectedTab].schema.properties),
...(selectedTab === 'slack'
? {
slack: {
description: '',
type: 'object',
format: 'resource-slack',
properties: {},
required: []
}
}
: {})
},
required: [
'app_path',
'startup_duration',
...(selectedTab === 'custom'
? customPathSchema.required
: notificationScripts[selectedTab].schema.required),
...(selectedTab === 'slack' ? ['slack'] : [])
],
type: 'object'
},
path: flowPath
}
})
await ScheduleService.createSchedule({
workspace: $workspaceStore!,
requestBody: {
path: flowPath,
schedule: formatCron(appReportingSchedule.cron),
timezone: appReportingSchedule.timezone,
script_path: flowPath,
is_flow: true,
args: {
app_path: appPath,
startup_duration: appReportingStartupDuration,
...args,
...(selectedTab === 'slack'
? {
slack: '$res:' + WORKSPACE_SLACK_BOT_TOKEN_PATH
}
: {})
},
enabled: true
}
})
appReportingEnabled = true
if (!skipToast) {
sendUserToast('App reporting enabled')
}
}
let disabled = true
$: disabled =
emptyString(appReportingSchedule.cron) ||
(selectedTab === 'custom' && emptyString(customPath)) ||
(selectedTab === 'slack' && !isSlackConnectedWorkspace) ||
!areArgsValid
</script>
<Drawer bind:open>
<DrawerContent title="Schedule Reports" on:close={() => (open = false)}
><svelte:fragment slot="actions">
<div class="mr-4 center-center -mt-2">
<Toggle
checked={appReportingEnabled}
options={{ right: 'enable', left: 'disable' }}
on:change={async () => {
if (appReportingEnabled) {
disableAppReporting()
} else {
enableAppReporting()
}
}}
{disabled}
/>
</div>
<Button
color="dark"
startIcon={{ icon: Save }}
size="sm"
on:click={async () => {
appReportingEnabled ? await updateAppReporting() : await enableAppReporting()
open = false
}}
{disabled}
>
{appReportingEnabled ? 'Update' : 'Save and enable'}
</Button>
</svelte:fragment>
<div class="flex flex-col gap-8">
<Alert type="info" title="Scheduled PDF reports"
>Send a PDF preview of the app at a given schedule. Enabling this feature will create a flow
and a schedule in your workspace.
<br /><br />
For the flow to be executed, you need to set the WORKER_GROUP environment variable of one of
your workers to "reports" or add the tag "chromium" to one of your worker groups.
</Alert>
<Section label="Reporting schedule">
<CronInput
bind:schedule={appReportingSchedule.cron}
bind:timezone={appReportingSchedule.timezone}
/>
</Section>
<Section
label="Startup duration in seconds"
tooltip="The number of seconds to wait before capturing a preview to ensure that all startup scripts
have been executed."
>
<div class="w-full pt-2">
<input
type="number"
class="text-sm w-full font-semibold"
bind:value={appReportingStartupDuration}
/>
</div>
</Section>
<Section label="Notification">
<Tabs bind:selected={selectedTab}>
<Tab value="custom">Custom</Tab>
<Tab value="email" disabled={!$enterpriseLicense}>
<div class="flex flex-row gap-1 items-center"
>Email{!$enterpriseLicense ? ' (EE only)' : ''}</div
>
</Tab>
<Tab value="slack" disabled={!$enterpriseLicense}
>Slack{!$enterpriseLicense ? ' (EE only)' : ''}</Tab
>
<Tab value="discord" disabled={!$enterpriseLicense}
>Discord{!$enterpriseLicense ? ' (EE only)' : ''}</Tab
>
</Tabs>
{#if selectedTab === 'custom'}
<div class="pt-2">
<ScriptPicker
on:select={(ev) => {
customPath = ev.detail.path
}}
initialPath={customPath}
allowRefresh
/>
</div>
<div class="prose text-2xs text-tertiary mt-2">
Pick a script that does whatever with the PDF report.
<br />
The script chosen is passed the parameters `pdf: string` and `app_path: string` where
`pdf` is the base64 encoded PDF report and `app_path` is the path of the app being
reported.
</div>
{/if}
{#if selectedTab === 'slack'}
<div class="pt-4">
{#if isSlackConnectedWorkspace}
<Alert type="info" title="Will use the Slack resource linked to the workspace" />
{:else}
<Alert type="error" title="Workspace not connected to Slack">
<div class="flex flex-row gap-x-1 w-full items-center">
<p class="text-clip grow min-w-0">
The workspace needs to be connected to Slack to use this feature. You can <a
target="_blank"
href="/workspace_settings?tab=slack">configure it here</a
>.
</p>
<Button
variant="border"
color="light"
on:click={getWorspaceSlackSetting}
startIcon={{ icon: RotateCw }}
/>
</div>
</Alert>
{/if}
</div>
{/if}
<div class="w-full pt-4">
{#if selectedTab !== 'custom' || customPath !== undefined}
{#key selectedTab + JSON.stringify(customPathSchema ?? {})}
<SchemaForm
bind:isValid={areArgsValid}
bind:args
schema={selectedTab !== 'custom'
? notificationScripts[selectedTab].schema
: customPathSchema}
/>
{/key}
{/if}
</div>
</Section>
</div>
</DrawerContent>
</Drawer>

View File

@@ -2721,22 +2721,7 @@ This is a paragraph.
icon: FileBarChart,
documentationLink: `${documentationBaseUrl}/statistic_card`,
dims: '2:4-3:4' as AppComponentDimensions,
customCss: {
title: {
class: '',
style: '',
quickCss: ['font-size: 1rem', 'font-size: 1.5rem', 'font-size: 2rem'],
quickTailwindClasses: ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl']
},
container: { class: '', style: '' },
value: {
class: '',
style: '',
quickCss: ['font-size: 1rem', 'font-size: 1.5rem', 'font-size: 2rem'],
quickTailwindClasses: ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl']
},
media: { class: '', style: '' }
},
customCss: {},
initialData: {
configuration: {
title: {
@@ -2754,40 +2739,11 @@ This is a paragraph.
value: 0,
fieldType: 'number'
},
media: {
type: 'oneOf',
selected: 'image',
labels: {
icon: 'Icon',
image: 'Image'
},
configuration: {
icon: {
icon: {
type: 'static',
value: undefined,
fieldType: 'icon-select'
}
},
image: {
source: {
type: 'static',
value: '/logo.svg',
fieldType: 'text',
fileUpload: {
accept: 'image/*',
convertTo: 'base64'
}
},
sourceKind: {
fieldType: 'select',
type: 'static',
selectOptions: selectOptions.imageSourceKind,
value: 'url' as (typeof selectOptions.imageSourceKind)[number]
}
}
}
} as const
icon: {
type: 'static',
value: undefined,
fieldType: 'icon-select'
}
}
}
}

View File

@@ -14,7 +14,6 @@
import Toggle from '$lib/components/Toggle.svelte'
import CssEval from './CssEval.svelte'
import parse from 'style-to-object'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
export let name: string
export let value: ComponentCssProperty = {}
@@ -47,207 +46,169 @@
}
let dynamicClass: boolean = value?.evalClass !== undefined
let render = 0
</script>
{#key render}
<div class=" border-b flex justify-between items-center p-2 text-xs leading-6 font-bold">
<div class="flex flex-col gap-1 w-full items-start">
<div class="flex flex-row h-8 items-center justify-between w-full">
<div class="capitalize">
{addWhitespaceBeforeCapitals(name)}
</div>
{#if shouldDisplayLeft}
<Button
color="light"
size="xs2"
variant="border"
on:click={() => {
dispatch('left')
}}
>
<div class="flex flex-row gap-2 text-2xs items-center">
<MoveLeft size={14} />
Copy for this component
</div>
</Button>
{/if}
{#if shouldDisplayRight}
<Button
color="light"
size="xs2"
variant="border"
on:click={() => {
dispatch('right')
}}
>
<div class="flex flex-row gap-2 text-2xs items-center">
Copy for every {componentType ? ccomponents[componentType].name : 'component'}
<MoveRight size={14} />
</div>
</Button>
{/if}
<div class=" border-b flex justify-between items-center p-2 text-xs leading-6 font-bold">
<div class="flex flex-col gap-1 w-full items-start">
<div class="flex flex-row h-8 items-center justify-between w-full">
<div class="capitalize">
{addWhitespaceBeforeCapitals(name)}
</div>
{#if wmClass}
<Badge small>
<div class="flex flex-row gap-1 items-center">
{wmClass}
<Button
color="light"
size="xs2"
on:click={() => {
copyToClipboard(wmClass)
}}
>
<Copy size={14} />
</Button>
{#if shouldDisplayLeft}
<Button
color="light"
size="xs2"
variant="border"
on:click={() => {
dispatch('left')
}}
>
<div class="flex flex-row gap-2 text-2xs items-center">
<MoveLeft size={14} />
Copy for this component
</div>
</Badge>
</Button>
{/if}
{#if shouldDisplayRight}
<Button
color="light"
size="xs2"
variant="border"
on:click={() => {
dispatch('right')
}}
>
<div class="flex flex-row gap-2 text-2xs items-center">
Copy for every {componentType ? ccomponents[componentType].name : 'component'}
<MoveRight size={14} />
</div>
</Button>
{/if}
</div>
{#if wmClass}
<Badge small>
<div class="flex flex-row gap-1 items-center">
{wmClass}
<Button
color="light"
size="xs2"
on:click={() => {
copyToClipboard(wmClass)
}}
>
<Copy size={14} />
</Button>
</div>
</Badge>
{/if}
</div>
</div>
{#if value}
<div class="p-2 flex flex-col">
{#if tooltip}
<div class="text-tertiary text-2xs py-2">{tooltip}</div>
{/if}
{#if value.style !== undefined || forceStyle}
<div class="pb-2">
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block w-full">
<div class="flex flex-row justify-between items-center w-full h-8">
<div class="text-xs font-medium text-tertiary"> Plain CSS </div>
{#if overriden}
<Badge color="red" small>Overriden by local</Badge>
{:else if overridding}
<Badge color="blue" small>Overriding global</Badge>
{/if}
</div>
{#if value}
<div class="p-2">
{#if tooltip}
<div class="text-tertiary text-2xs py-2">{tooltip}</div>
{/if}
{#if value.style !== undefined || forceStyle}
<div class="pb-2">
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block w-full">
<div class="flex flex-row justify-between items-center w-full h-8">
<div class="text-xs font-medium text-tertiary"> Plain CSS </div>
{#if overriden}
<Badge color="red" small>Overriden by local</Badge>
{:else if overridding}
<Badge color="blue" small>Overriding global</Badge>
{/if}
</div>
<div class="flex gap-1">
<div class="relative grow">
<ClearableInput
bind:value={value.style}
type="textarea"
wrapperClass="h-full min-h-[72px]"
inputClass="h-full !text-xs !rounded-none !p-2"
/>
</div>
<div class="flex flex-col gap-1">
{#if quickStyleProperties?.length}
<Popover placement="bottom" notClickable disappearTimeout={0}>
<Button
variant="border"
color="light"
size="xs"
btnClasses="!p-1 !w-[34px] !h-[34px] {isQuickMenuOpen
? '!bg-gray-200/60 hover:!bg-gray-200 focus:!bg-gray-200'
: ''}"
aria-label="{isQuickMenuOpen ? 'Close' : 'Open'} styling menu"
on:click={toggleQuickMenu}
>
<Paintbrush2 size={18} />
</Button>
<svelte:fragment slot="text">
{isQuickMenuOpen ? 'Close' : 'Open'} styling menu
</svelte:fragment>
</Popover>
{/if}
</div>
</div>
</label>
{#if quickStyleProperties?.length && isQuickMenuOpen}
<div transition:fade|local={{ duration: 200 }} class="w-full pt-1">
<QuickStyleMenu
<div class="flex gap-1">
<div class="relative grow">
<ClearableInput
bind:value={value.style}
properties={quickStyleProperties}
{componentType}
componentProperty={name}
type="textarea"
wrapperClass="h-full min-h-[72px]"
inputClass="h-full !text-xs !rounded-none !p-2"
/>
</div>
{/if}
{#if value.quickCss}
<div class="flex flex-row gap-1 items-center mt-1 flex-wrap">
{#each value.quickCss as v}
<Badge
small
baseClass="cursor-pointer"
on:click={() => {
value.style = value.style === '' ? `${v};` : `${value.style} ${v};`
}}
>
{v}
</Badge>
{/each}
<div class="flex flex-col gap-1">
{#if quickStyleProperties?.length}
<Popover placement="bottom" notClickable disappearTimeout={0}>
<Button
variant="border"
color="light"
size="xs"
btnClasses="!p-1 !w-[34px] !h-[34px] {isQuickMenuOpen
? '!bg-gray-200/60 hover:!bg-gray-200 focus:!bg-gray-200'
: ''}"
aria-label="{isQuickMenuOpen ? 'Close' : 'Open'} styling menu"
on:click={toggleQuickMenu}
>
<Paintbrush2 size={18} />
</Button>
<svelte:fragment slot="text">
{isQuickMenuOpen ? 'Close' : 'Open'} styling menu
</svelte:fragment>
</Popover>
{/if}
</div>
{/if}
</div>
{/if}
{#if value.class !== undefined || forceClass}
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block">
<div class="text-xs font-medium text-tertiary">
Tailwind classes
<Tooltip light documentationLink="https://tailwindcss.com/">
Use any tailwind classes to style your component
</Tooltip>
</div>
<div class="relative">
<SimpleEditor
class="h-24"
lang="tailwindcss"
bind:code={value.class}
fixedOverflowWidgets={true}
small
automaticLayout
deno={false}
subType="tailwind"
</label>
{#if quickStyleProperties?.length && isQuickMenuOpen}
<div transition:fade|local={{ duration: 200 }} class="w-full pt-1">
<QuickStyleMenu
bind:value={value.style}
properties={quickStyleProperties}
{componentType}
componentProperty={name}
/>
</div>
{#if value.quickTailwindClasses}
<div class="flex flex-row gap-1 items-center mt-1 flex-wrap">
{#each value.quickTailwindClasses as cls}
<Badge
baseClass="cursor-pointer"
small
on:click={() => {
value.class = value.class === '' ? cls : `${value.class} ${cls}`
render++
}}
>
{cls}
</Badge>
{/each}
</div>
{/if}
</label>
{/if}
{/if}
</div>
{/if}
<Toggle
options={{
right: 'Use dynamic class'
}}
size="xs"
bind:checked={dynamicClass}
on:change={(e) => {
if (e.detail && !value.evalClass) {
value.evalClass = {
type: 'evalv2',
expr: '',
connections: [],
fieldType: 'text'
}
} else {
value.evalClass = undefined
{#if value.class !== undefined || forceClass}
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block">
<div class="text-xs font-medium text-tertiary">
Tailwind classes
<Tooltip light documentationLink="https://tailwindcss.com/">
Use any tailwind classes to style your component
</Tooltip>
</div>
<div class="relative">
<ClearableInput
bind:value={value.class}
type="textarea"
wrapperClass="h-full"
inputClass="h-full !text-xs !rounded-none !p-2 min-h-[72px]"
/>
</div>
</label>
{/if}
<Toggle
options={{
right: 'Use dynamic class'
}}
size="xs"
bind:checked={dynamicClass}
on:change={(e) => {
if (e.detail && !value.evalClass) {
value.evalClass = {
type: 'evalv2',
expr: '',
connections: [],
fieldType: 'text'
}
}}
/>
} else {
value.evalClass = undefined
}
}}
/>
{#if value?.evalClass && dynamicClass}
<CssEval bind:evalClass={value.evalClass} />
{/if}
</div>
{/if}
{/key}
{#if value?.evalClass && dynamicClass}
<CssEval bind:evalClass={value.evalClass} />
{/if}
</div>
{/if}

View File

@@ -1,5 +1,35 @@
import * as csstree from 'css-tree'
import type { ComponentCssProperty } from '../../types'
export function sanitizeCss(css: string, authorizedClassNames: string[]) {
const ast = csstree.parse(css)
const removedClassNames: string[] = []
csstree.walk(ast, (node: any, item, list) => {
if (node.type === 'Rule') {
let shouldRemoveRule = true
csstree.walk(node, (innerNode: any) => {
if (innerNode.type === 'ClassSelector' && authorizedClassNames.includes(innerNode.name)) {
shouldRemoveRule = false
}
if (shouldRemoveRule && innerNode.name) {
removedClassNames.push(innerNode.name)
}
})
if (shouldRemoveRule) {
list.remove(item)
}
}
})
return {
css: csstree.generate(ast),
removedClassNames
}
}
export const authorizedClassnames = [
'wm-container',
'wm-list',

View File

@@ -89,7 +89,6 @@
placeholder={config?.['placeholder']}
customTitle={config?.['customTitle']}
tooltip={config?.['tooltip']}
fileUpload={config?.['fileUpload']}
/>
{/if}
{/each}

View File

@@ -44,8 +44,6 @@ export type ComponentCssProperty = {
class?: string
style?: string
evalClass?: RichConfiguration
quickCss?: readonly string[] | undefined
quickTailwindClasses?: readonly string[] | undefined
}
export type ComponentCustomCSS<T extends keyof typeof components> = Partial<
@@ -175,7 +173,7 @@ export type ListContext = Writable<{
disabled: boolean
}>
export type ListInputs = { set: (id: string, value: any) => void; remove: (id: string) => void }
export type ListInputs = {set: (id: string, value: any) => void, remove: (id: string) => void}
export type GroupContext = Writable<Record<string, any>>

View File

@@ -365,20 +365,3 @@ export function transformBareBase64IfNecessary(source: string | undefined) {
return `data:application/octet-stream;base64,${source}`
}
}
export function getImageDataURL(imageKind: string | undefined, image: string | undefined) {
if (!imageKind || !image) {
return null
}
switch (imageKind) {
case 'png encoded as base64':
return 'data:image/png;base64,' + image
case 'jpeg encoded as base64':
return 'data:image/jpeg;base64,' + image
case 'svg encoded as base64':
return 'data:image/svg+xml;base64,' + image
default:
return image
}
}

View File

@@ -60,7 +60,7 @@
<svelte:component this={icons[type]} class={classes[type].iconClass} size={16} />
</div>
<div class="ml-2 pt-[0.15rem] w-full">
<div class="ml-2 w-full">
<span
class={classNames(
size === 'sm' ? 'text-sm' : 'text-xs ',
@@ -72,17 +72,15 @@
<Tooltip {documentationLink} scale={0.9}>{tooltip}</Tooltip>
{/if}
</span>
{#if $$slots.default}
<div
class={classNames(
size === 'sm' ? 'text-sm' : 'text-xs ',
'mt-2',
classes[type].descriptionClass
)}
>
<slot />
</div>
{/if}
<div
class={classNames(
size === 'sm' ? 'text-sm' : 'text-xs ',
'mt-2',
classes[type].descriptionClass
)}
>
<slot />
</div>
</div>
</div>
</div>

View File

@@ -149,7 +149,14 @@
floatingConfig={{
middleware: [
autoPlacement({
allowedPlacements: ['bottom-end', 'top-end']
allowedPlacements: [
'bottom-start',
'bottom-end',
'top-start',
'top-end',
'top',
'bottom'
]
})
]
}}

View File

@@ -1,8 +1,7 @@
system: |-
You are a helpful coding assistant for Windmill, a developer platform for running scripts. You modify code as instructed by the user. Each user message includes some contextual information which should guide your answer.
You are a helpful coding assistant for Windmill, a developer platform for running scripts. You fix the code shared by the user. Each user message includes some contextual information which should guide your answer.
Only output code. Wrap the code in a code block.
Put explanations directly in the code as comments.
Return the complete modified code.
Here's how interactions have to look like:
user: {sample_question}

View File

@@ -1,5 +1,5 @@
export const EDIT_PROMPT = {
"system": "You are a helpful coding assistant for Windmill, a developer platform for running scripts. You modify code as instructed by the user. Each user message includes some contextual information which should guide your answer.\nOnly output code. Wrap the code in a code block. \nPut explanations directly in the code as comments.\nReturn the complete modified code.\n\nHere's how interactions have to look like:\nuser: {sample_question}\nassistant: ```language\n{code}\n```",
"system": "You are a helpful coding assistant for Windmill, a developer platform for running scripts. You fix the code shared by the user. Each user message includes some contextual information which should guide your answer.\nOnly output code. Wrap the code in a code block. \nPut explanations directly in the code as comments.\n\nHere's how interactions have to look like:\nuser: {sample_question}\nassistant: ```language\n{code}\n```",
"prompts": {
"python3": {
"prompt": "Here's my python3 code: \n```python\n{code}\n```\n<contextual_information>\nYou have to write a function in python called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result.\nYou can take as parameters resources which are dictionaries containing credentials or configuration information. For Windmill to correctly detect the resources to be passed, the resource type name has to be exactly as specified in the following list:\n<resourceTypes>\n{resourceTypes}\n</resourceTypes>\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nThe resource type name has to be exactly as specified (has to be IN LOEWRCASE). If the type name conflicts with any imported methods, you have to rename the imported method with the conflicting name.\n<contextual_information>\nMy instructions: {description}"

View File

@@ -3,7 +3,6 @@ system: |-
Only output code. Wrap the code in a code block.
Explain the error and the fix after generating the code inside an <explanation> tag.
Also put explanations directly in the code as comments.
Return the complete fixed code.
Here's how interactions have to look like:
user: {sample_question}

View File

@@ -1,5 +1,5 @@
export const FIX_PROMPT = {
"system": "You are a helpful coding assistant for Windmill, a developer platform for running scripts. You fix the code shared by the user. Each user message includes some contextual information which should guide your answer.\nOnly output code. Wrap the code in a code block. \nExplain the error and the fix after generating the code inside an <explanation> tag.\nAlso put explanations directly in the code as comments.\nReturn the complete fixed code.\n\nHere's how interactions have to look like:\nuser: {sample_question}\nassistant: ```language\n{code}\n```\n<explanation>{explanation}</explanation>",
"system": "You are a helpful coding assistant for Windmill, a developer platform for running scripts. You fix the code shared by the user. Each user message includes some contextual information which should guide your answer.\nOnly output code. Wrap the code in a code block. \nExplain the error and the fix after generating the code inside an <explanation> tag.\nAlso put explanations directly in the code as comments.\n\nHere's how interactions have to look like:\nuser: {sample_question}\nassistant: ```language\n{code}\n```\n<explanation>{explanation}</explanation>",
"prompts": {
"python3": {
"prompt": "Here's my python3 code: \n```python\n{code}\n```\n<contextual_information>\nYou have to write a function in python called \"main\". Specify the parameter types. Do not call the main function. You should generally return the result.\nYou can take as parameters resources which are dictionaries containing credentials or configuration information. For Windmill to correctly detect the resources to be passed, the resource type name has to be exactly as specified in the following list:\n<resourceTypes>\n{resourceTypes}\n</resourceTypes>\nYou need to define the type of the resources that are needed before the main function, but only include them if they are actually needed to achieve the function purpose.\nThe resource type name has to be exactly as specified (has to be IN LOEWRCASE). If the type name conflicts with any imported methods, you have to rename the imported method with the conflicting name.\n<contextual_information>\nI get the following error: {error}\nFix my code."

View File

@@ -4,12 +4,9 @@
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import Tooltip from '../Tooltip.svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import { AlertCircle, CheckCircle2, ChevronDown, Filter, PlayCircle, X } from 'lucide-svelte'
import { ChevronDown, Filter, X } from 'lucide-svelte'
import JsonEditor from '../apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
import Toggle from '../Toggle.svelte'
import Label from '../Label.svelte'
import Section from '../Section.svelte'
import CloseButton from '../common/CloseButton.svelte'
// Filters
export let path: string | null = null
@@ -22,8 +19,6 @@
export let jobKindsCat: string
export let user: string | null = null
export let folder: string | null = null
export let hideSchedules: boolean | undefined = undefined
export let mobile: boolean = false
// Autocomplete data
export let paths: string[] = []
@@ -51,117 +46,119 @@
}
</script>
<div class="flex gap-4">
{#if !mobile}
<div class="flex gap-2">
<div class="relative">
<span class="text-xs absolute -top-4">Filter by</span>
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!manuallySet) {
path = null
user = null
folder = null
} else {
manuallySet = false
}
}}
>
<ToggleButton value="path" label="Path" />
<ToggleButton value="user" label="User" />
<ToggleButton value="folder" label="Folder" />
</ToggleButtonGroup>
</div>
{#if filterBy == 'user'}
{#key user}
<div class="relative">
{#if user}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
user = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">User</span>
<AutoComplete
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{:else if filterBy == 'folder'}
{#key folder}
<div class="relative">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
folder = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">Folder</span>
<AutoComplete
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{:else if filterBy === 'path'}
{#key path}
<div class="relative">
{#if path}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
path = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">Path</span>
<AutoComplete
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{/if}
<div class="flex flex-col items-start gap-6 xl:gap-2 xl:flex-row mt-4 xl:mt-0">
<div class="flex flex-col xl:flex-row gap-6 xl:gap-2 w-full">
<div class="relative">
<span class="text-xs absolute -top-4">Filter by</span>
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!manuallySet) {
path = null
user = null
folder = null
} else {
manuallySet = false
}
}}
>
<ToggleButton value="path" label="Path" />
<ToggleButton value="user" label="User" />
<ToggleButton value="folder" label="Folder" />
</ToggleButtonGroup>
</div>
</div>
{#if filterBy == 'user'}
{#key user}
<div class="relative">
{#if user}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
user = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">User</span>
<AutoComplete
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[30px] py-1 !text-xs !w-64"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{/if}
{#if filterBy == 'folder'}
{#key folder}
<div class="relative">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
folder = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">Folder</span>
<AutoComplete
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[30px] py-1 !text-xs !w-64"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{/if}
{#if filterBy === 'path'}
{#key path}
<div class="relative">
{#if path}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
path = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">Path</span>
<AutoComplete
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[30px] py-1 !text-xs !w-64"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{/if}
<div class="flex flex-col xl:flex-row gap-6 xl:gap-2 w-full">
<div class="relative">
<span class="text-xs absolute -top-4">Kind</span>
<ToggleButtonGroup bind:selected={jobKindsCat}>
@@ -190,238 +187,84 @@
<span class="text-xs absolute -top-4">Status</span>
<ToggleButtonGroup bind:selected={success}>
<ToggleButton value={undefined} label="All" />
<ToggleButton
value={'running'}
label="Running"
class="whitespace-nowrap"
icon={PlayCircle}
iconProps={{ color: success === 'running' ? 'blue' : 'gray' }}
/>
<ToggleButton
value={'success'}
label="Success"
class="whitespace-nowrap"
icon={CheckCircle2}
iconProps={{ color: success === 'success' ? 'green' : 'gray' }}
/>
<ToggleButton
value={'failure'}
label="Failure"
class="whitespace-nowrap"
icon={AlertCircle}
iconProps={{ color: success === 'failure' ? 'red' : 'gray' }}
/>
<ToggleButton value={'running'} label="Running" class="whitespace-nowrap" />
<ToggleButton value={'success'} label="Success" class="whitespace-nowrap" />
<ToggleButton value={'failure'} label="Failure" class="whitespace-nowrap" />
</ToggleButtonGroup>
</div>
{/if}
<div class="relative w-32">
<span class="text-xs absolute -top-4"> Show Skipped Flows </span>
<div class="flex flex-row gap-1 items-center">
<Toggle size="xs" bind:checked={isSkipped} />
<Tooltip>Skipped flows are flows that did an early break</Tooltip>
</div>
</div>
</div>
<Popup
floatingConfig={{ strategy: 'absolute', placement: 'bottom-end' }}
containerClasses="border rounded-lg shadow-lg p-6 bg-surface"
containerClasses="border rounded-lg shadow-lg p-4 bg-surface"
let:close
>
<svelte:fragment slot="button">
<Button color="dark" size="xs" nonCaptureEvent={true} startIcon={{ icon: Filter }}>
More filters
<Button
color="light"
size="xs"
nonCaptureEvent={true}
variant="border"
startIcon={{ icon: Filter }}
>
Filter by args/result
</Button>
</svelte:fragment>
<Section label="Filters">
<svelte:fragment slot="action">
<CloseButton on:close={() => close(null)} />
</svelte:fragment>
<div class="w-80 flex flex-col gap-4">
{#if mobile}
<Label label="Filter by">
<ToggleButtonGroup
bind:selected={filterBy}
on:selected={() => {
if (!manuallySet) {
path = null
user = null
folder = null
} else {
manuallySet = false
}
}}
>
<ToggleButton value="path" label="Path" />
<ToggleButton value="user" label="User" />
<ToggleButton value="folder" label="Folder" />
</ToggleButtonGroup>
</Label>
{#if filterBy == 'user'}
{#key user}
<Label label="User">
<div class="relative w-full">
{#if user}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
user = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
</Label>
{/key}
{:else if filterBy == 'folder'}
{#key folder}
<Label label="Folder">
<div class="relative w-full">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
folder = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
</Label>
{/key}
{:else if filterBy === 'path'}
{#key path}
<Label label="Path">
<div class="relative w-full">
{#if path}
<button
class="absolute top-2 right-2 z-50"
on:click={() => {
path = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
{/if}
<Label label="Kind">
<ToggleButtonGroup bind:selected={jobKindsCat}>
<ToggleButton value="all" label="All" />
<ToggleButton
value="runs"
label="Runs"
showTooltipIcon
tooltip="Runs are jobs that have no parent jobs (flows are jobs that are parent of the jobs they start), they have been triggered through the UI, a schedule or webhook"
/>
<ToggleButton
value="previews"
label="Previews"
showTooltipIcon
tooltip="Previews are jobs that have been started in the editor as 'Tests'"
/>
<ToggleButton
value="dependencies"
label="Deps"
showTooltipIcon
tooltip="Deploying a script, flow or an app launch a dependency job that create and then attach the lockfile to the deployed item. This mechanism ensure that logic is always executed with the exact same direct and indirect dependencies."
/>
</ToggleButtonGroup>
</Label>
<Label label="Status">
<ToggleButtonGroup bind:selected={success}>
<ToggleButton value={undefined} label="All" />
<ToggleButton value={'running'} label="Running" class="whitespace-nowrap" />
<ToggleButton value={'success'} label="Success" class="whitespace-nowrap" />
<ToggleButton value={'failure'} label="Failure" class="whitespace-nowrap" />
</ToggleButtonGroup>
</Label>
{/if}
<Label label="Show Skipped Flows">
<div class="flex flex-row gap-1 items-center">
<Toggle size="xs" bind:checked={isSkipped} />
<Tooltip>Skipped flows are flows that did an early break</Tooltip>
</div>
</Label>
<Label label="Hide future scheduled jobs from schedules">
<Toggle size="xs" bind:checked={hideSchedules} />
</Label>
<span class="text-xs leading-6">
{`Filter by a json being a subset of the args/result. Try '\{"foo": "bar"\}'`}
</span>
<Label label="Filter by args">
<JsonEditor on:change bind:error={argError} bind:code={copyArgFilter} />
</Label>
<Label label="Filter by result">
<JsonEditor on:change bind:error={resultError} bind:code={copyResultFilter} />
</Label>
<div class="flex flex-row gap-2 justify-between">
<Button
size="xs"
color="light"
on:click={() => {
argFilter = ''
resultFilter = ''
close(null)
}}
>
Clear
</Button>
<Button
size="xs"
color="dark"
on:click={() => {
argFilter = copyArgFilter
resultFilter = copyResultFilter
close(null)
}}
>
Set
</Button>
</div>
<div class="flex flex-col w-72 p-2 gap-2">
<div class="flex flex-row w-full items-center justify-between">
<span class="text-sm eading-6 font-semibold">Filters</span>
<Button
size="xs"
color="light"
on:click={() => {
close(null)
}}
>
Close
</Button>
</div>
</Section>
<span class="text-xs leading-6">
{`Filter by a json being a subset of the args/result. Try '\{"foo": "bar"\}'`}
</span>
<span class="text-xs eading-6 font-semibold">Filter by args</span>
<JsonEditor on:change bind:error={argError} bind:code={copyArgFilter} />
<span class="text-xs eading-6 font-semibold">Filter by result</span>
<JsonEditor on:change bind:error={resultError} bind:code={copyResultFilter} />
<div class="flex flex-row gap-2 justify-between">
<Button
size="xs"
color="light"
on:click={() => {
argFilter = ''
resultFilter = ''
close(null)
}}
>
Clear
</Button>
<Button
size="xs"
color="dark"
on:click={() => {
argFilter = copyArgFilter
resultFilter = copyResultFilter
close(null)
}}
>
Set
</Button>
</div>
</div>
</Popup>
</div>

View File

@@ -38,8 +38,3 @@ export const SCRIPT_VIEW_WEBHOOK_INFO_LINK = 'https://www.windmill.dev/docs/core
export const SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS = true
export const SCRIPT_CUSTOMISE_SHOW_KIND = true
export const WORKER_S3_BUCKET_SYNC_SETTING = 'worker_s3_bucket_sync'
export const CUSTOM_TAGS_SETTING = 'custom_tags'
export const WORKSPACE_SLACK_BOT_TOKEN_PATH = 'f/slack_bot/bot_token'

View File

@@ -210,12 +210,12 @@ function argSigToJsonSchemaType(
export async function loadSchemaFromPath(path: string, hash?: string): Promise<Schema> {
if (path.startsWith('hub/')) {
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
if (schema && 'properties' in schema) {
return schema
} else {
if (language == 'deno' || language == 'nativets') {
const newSchema = emptySchema()
await inferArgs(language as SupportedLanguage, content ?? '', newSchema)
await inferArgs('deno' as SupportedLanguage, content ?? '', newSchema)
return newSchema
} else {
return schema ?? emptySchema()
}
} else if (hash) {
const script = await ScriptService.getScriptByHash({

View File

@@ -256,7 +256,6 @@
</div>
<div
id="sidebar"
class={classNames(
'hidden md:flex md:flex-col md:fixed md:inset-y-0 transition-all ease-in-out duration-200 shadow-md z-40 ',
isCollapsed ? 'md:w-12' : 'md:w-40',
@@ -308,7 +307,6 @@
</div>
</div>
<div
id="content"
class={classNames(
'w-full flex flex-col flex-1 h-full',
devOnly ? '!pl-0' : isCollapsed ? 'md:pl-12' : 'md:pl-40',

View File

@@ -54,7 +54,7 @@
noBackend={false}
/>
{#if can_write}
<div id="app-edit-btn" class="absolute bottom-4 z-50 right-4">
<div class="absolute bottom-4 z-50 right-4">
<Button size="sm" startIcon={{ icon: Pen }} variant="border" href="/apps/edit/{app.path}"
>Edit</Button
>

View File

@@ -25,12 +25,12 @@
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import RunsFilter from '$lib/components/runs/RunsFilter.svelte'
import MobileFilters from '$lib/components/runs/MobileFilters.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import { tweened, type Tweened } from 'svelte/motion'
import { goto } from '$app/navigation'
import RunsQueue from '$lib/components/runs/RunsQueue.svelte'
import { forLater } from '$lib/forLater'
let jobs: Job[] | undefined
let intervalId: NodeJS.Timeout | undefined
@@ -50,11 +50,6 @@
? $page.url.searchParams.get('is_skipped') == 'true'
: false
let hideSchedules: boolean | undefined =
$page.url.searchParams.get('hide_scheduled') != undefined
? $page.url.searchParams.get('hide_scheduled') == 'true'
: false
let argFilter: any = $page.url.searchParams.get('arg')
? JSON.parse(decodeURIComponent($page.url.searchParams.get('arg') ?? '{}'))
: undefined
@@ -84,10 +79,6 @@
searchParams.set('is_skipped', isSkipped.toString())
}
if (hideSchedules) {
searchParams.set('hide_scheduled', hideSchedules.toString())
}
// ArgFilter is an object. Encode it to a string
argFilter && searchParams.set('arg', encodeURIComponent(JSON.stringify(argFilter)))
resultFilter && searchParams.set('result', encodeURIComponent(JSON.stringify(resultFilter)))
@@ -121,7 +112,7 @@
}
$: ($workspaceStore && loadJobs()) ||
(path && success && isSkipped && jobKinds && user && folder && minTs && maxTs && hideSchedules)
(path && success && isSkipped && jobKinds && user && folder && minTs && maxTs)
async function fetchJobs(
startedBefore: string | undefined,
@@ -155,14 +146,7 @@
getCount()
try {
jobs = await fetchJobs(maxTs, minTs)
computeCompletedJobs()
if (hideSchedules && !schedulePath) {
jobs = jobs.filter(
(job) => !(job && 'running' in job && job.scheduled_for && forLater(job.scheduled_for))
)
}
} catch (err) {
sendUserToast(`There was a problem fetching jobs: ${err}`, true)
console.error(JSON.stringify(err))
@@ -212,13 +196,6 @@
.forEach((x) => (jobs![jobs?.findIndex((y) => y.id == x.id)!] = x))
jobs = jobs
computeCompletedJobs()
if (hideSchedules && !schedulePath) {
jobs = jobs.filter(
(job) =>
!(job && 'running' in job && job.scheduled_for && forLater(job.scheduled_for))
)
}
}
loading = false
}
@@ -242,11 +219,6 @@
? $page.url.searchParams.get('is_skipped') == 'true'
: false
hideSchedules =
$page.url.searchParams.get('hide_scheduled') != undefined
? $page.url.searchParams.get('hide_scheduled') == 'true'
: false
argFilter = $page.url.searchParams.get('arg')
? JSON.parse(decodeURIComponent($page.url.searchParams.get('arg') ?? '{}'))
: undefined
@@ -429,7 +401,7 @@
<svelte:window bind:innerWidth />
{#if innerWidth > 1280}
{#if innerWidth > 768}
<div class="w-full h-screen">
<div class="px-2">
<div class="flex items-center space-x-2 flex-row justify-between">
@@ -443,23 +415,47 @@
your own runs or runs of groups you belong to unless you are an admin.
</Tooltip>
</div>
<RunsFilter
bind:isSkipped
bind:user
bind:folder
bind:path
bind:success
bind:argFilter
bind:resultFilter
bind:argError
bind:resultError
bind:jobKindsCat
bind:hideSchedules
on:change={reloadLogsWithoutFilterError}
{usernames}
{folders}
{paths}
/>
<div class="hidden xl:block">
<RunsFilter
bind:isSkipped
bind:user
bind:folder
bind:path
bind:success
bind:argFilter
bind:resultFilter
bind:argError
bind:resultError
bind:jobKindsCat
on:change={reloadLogsWithoutFilterError}
{usernames}
{folders}
{paths}
/>
</div>
<div class="xl:hidden">
<MobileFilters>
<svelte:fragment slot="filters">
<span class="text-xs font-semibold leading-6">Filters</span>
<RunsFilter
bind:isSkipped
{paths}
{usernames}
{folders}
bind:jobKindsCat
bind:folder
bind:path
bind:user
bind:success
bind:argFilter
bind:resultFilter
bind:argError
bind:resultError
on:change={reloadLogsWithoutFilterError}
/>
</svelte:fragment>
</MobileFilters>
</div>
</div>
</div>
@@ -630,24 +626,47 @@
your own runs or runs of groups you belong to unless you are an admin.
</Tooltip>
</div>
<RunsFilter
bind:isSkipped
{paths}
{usernames}
{folders}
bind:jobKindsCat
bind:folder
bind:path
bind:user
bind:success
bind:argFilter
bind:resultFilter
bind:argError
bind:resultError
bind:hideSchedules
mobile={true}
on:change={reloadLogsWithoutFilterError}
/>
<div class="hidden 2xl:block">
<RunsFilter
bind:isSkipped
bind:user
bind:folder
bind:path
bind:success
bind:argFilter
bind:resultFilter
bind:argError
bind:resultError
bind:jobKindsCat
on:change={reloadLogsWithoutFilterError}
{usernames}
{folders}
{paths}
/>
</div>
<div class="block 2xl:hidden">
<MobileFilters>
<svelte:fragment slot="filters">
<span class="text-xs font-semibold leading-6">Filters</span>
<RunsFilter
bind:isSkipped
{paths}
{usernames}
{folders}
bind:jobKindsCat
bind:folder
bind:path
bind:user
bind:success
bind:argFilter
bind:resultFilter
bind:argError
bind:resultError
on:change={reloadLogsWithoutFilterError}
/>
</svelte:fragment>
</MobileFilters>
</div>
</div>
</div>
<div class="p-2 w-full">

View File

@@ -9,7 +9,6 @@
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import WorkspaceGroup from '$lib/components/WorkspaceGroup.svelte'
import { CUSTOM_TAGS_SETTING, WORKER_S3_BUCKET_SYNC_SETTING } from '$lib/consts'
import { WorkerService, type WorkerPing, SettingService, ConfigService } from '$lib/gen'
import { enterpriseLicense, superadmin } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
@@ -37,6 +36,9 @@
(x) => x[0]?.split(splitter)?.[0]
)
const WORKER_S3_BUCKET_SYNC_SETTING = 'worker_s3_bucket_sync'
const CUSTOM_TAGS_SETTING = 'custom_tags'
let timeSinceLastPing = 0
async function loadWorkers(): Promise<void> {

View File

@@ -792,11 +792,11 @@ const config = {
backgroundColor: lightTheme.border + ' !important',
margin: '0 !important',
border: 'none !important',
zIndex: '1001 !important',
'&::after': {
backgroundColor: lightTheme.border + ' !important',
margin: '0 !important',
transform: 'none !important',
zIndex: '1001 !important',
transition: 'opacity 200ms !important',
opacity: '0',
'--splitter-hover-size': '5px',

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.217.0"
wmill_pg = ">=1.217.0"
wmill = ">=1.216.0"
wmill_pg = ">=1.216.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.217.0
version: 1.216.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.217.0"
version = "1.216.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

Binary file not shown.

Binary file not shown.

View File

@@ -577,11 +577,11 @@ def get_resource(
@init_global_client
def set_resource(*args, **kwargs) -> None:
def set_resource(**kwargs) -> None:
"""
Set the resource at a given path as a string, creating it if it does not exist
"""
return _client.set_resource(*args, **kwargs)
return _client.set_resource(**kwargs)
@init_global_client
@@ -592,33 +592,33 @@ def set_state(value: Any) -> None:
return _client.set_state(value)
def set_shared_state_pickle(*args, **kwargs) -> None:
def set_shared_state_pickle(**kwargs) -> None:
"""
Set the state in the shared folder using pickle
"""
return Windmill.set_shared_state_pickle(args, **kwargs)
return Windmill.set_shared_state_pickle(**kwargs)
@deprecate("Windmill.get_shared_state_pickle(...)")
def get_shared_state_pickle(*args, **kwargs) -> Any:
def get_shared_state_pickle(**kwargs) -> Any:
"""
Get the state in the shared folder using pickle
"""
return Windmill.get_shared_state_pickle(*args, **kwargs)
return Windmill.get_shared_state_pickle(**kwargs)
def set_shared_state(*args, **kwargs) -> None:
def set_shared_state(**kwargs) -> None:
"""
Set the state in the shared folder using pickle
"""
return Windmill.set_shared_state(*args, **kwargs)
return Windmill.set_shared_state(**kwargs)
def get_shared_state(*args, **kwargs) -> None:
def get_shared_state(**kwargs) -> None:
"""
Set the state in the shared folder using pickle
"""
return Windmill.get_shared_state(*args, **kwargs)
return Windmill.get_shared_state(**kwargs)
@init_global_client

View File

@@ -0,0 +1,138 @@
import functools
import ast
import inspect
import yaml
from graphlib import TopologicalSorter
def flow(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
class Edge(object):
def __init__(self, _node, _path):
self._node = _node
self._path = _path
# print("DECL EDGE: node: " + str(self._node) + " path: " + str(self._path))
def __getattr__(self, attr):
return Edge(self._node, self._path + [attr])
def __str__(self):
return f"EDGE: {self._node}({self._path})"
class Node(object):
def __init__(self, _id, _deps, _f):
self._id = _id
self._deps = _deps
self._f = _f
# print("DECL NODE: id: " + str(self._id) + ", deps: " + str([f"{k}={v.__str__()}" for k, v in self._deps.items()]))
def __getattr__(self, attr):
return Edge(self, [attr])
def __str__(self):
k_v = [f"{k}={v.__str__()}" for k, v in self._deps.items()]
return f"{self._id}({k_v})"
def step(f):
# print("DECL F: " + f.__name__)
# code = inspect.getsource(f)
# print(code)
# print(ast.dump(ast.parse(code)))
# print(ast.unparse(ast.parse(code)))
@functools.wraps(f)
def wrapper(*args, **kwargs):
kwargs.update(dict(zip(f.__code__.co_varnames, args)))
for k, v in kwargs.items():
if isinstance(v, Node):
kwargs[k] = Edge(v, [])
return Node(f.__name__, kwargs, f)
return wrapper
@step
def d():
return 32
@step
def a(a):
import pandas
return 32
@step
def b(b):
import pandas
return 32
@step
def c(x, y):
return x + y + 1
def flow():
r_a = a(d())
r_b = b(2)
r_c = c(r_a, r_b)
return r_c
def get_edges_and_nodes(graph):
edges = {}
nodes = [graph]
all_nodes = []
seen = set()
while nodes:
node = nodes.pop()
id = node._id
if id in seen:
continue
all_nodes.append(node)
seen.add(id)
for _, v in node._deps.items():
if isinstance(v, Edge):
if id not in edges:
edges[id] = []
edges[id].append(v._node._id)
nodes.append(v._node)
return edges, {n._id: n for n in all_nodes}
def render(graph):
edges, all_nodes = get_edges_and_nodes(graph)
# print(edges)
# print(all_nodes)
ts = TopologicalSorter(edges)
sortd = tuple(ts.static_order())
modules_f = [[id, all_nodes[id]] for id in sortd]
files = [ n._f for n in all_nodes.values()]
sources = gen_sources(files)
print(sources)
modules = [{"id": k, "lang": "python", "content": render_f(v._f)} for k, v in modules_f]
return yaml.dump(modules)
def gen_module_sources(source):
pass
def gen_sources(files):
files = set(files)
asts = []
sources = {}
for f in files:
asts.append(ast.parse(inspect.getsource(f)))
for astf in asts:
for node in astf.body:
if isinstance(node, ast.FunctionDef):
print(node.args)
code = ast.unparse(node.body)
sources[node.name] = code
return sources
def render_f(f):
code = inspect.getsource(f)
return ast.unparse(ast.parse(code))
rendered = render(flow())

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.217.0"
version = "1.216.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.217.0",
"version": "1.216.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {

View File

@@ -1 +1 @@
1.217.0
1.216.0