Compare commits
187 Commits
v1.60.0
...
rf/fixGrap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fceae4f85 | ||
|
|
c1cfed840a | ||
|
|
0921ba0085 | ||
|
|
14b32be8b2 | ||
|
|
b67550a790 | ||
|
|
06347746c0 | ||
|
|
0b4288ad9c | ||
|
|
a6dcb218ff | ||
|
|
6fe93dac58 | ||
|
|
202b04bb08 | ||
|
|
df8cc1f248 | ||
|
|
61e3214f9e | ||
|
|
d663884a99 | ||
|
|
a2ed69f87f | ||
|
|
f377c84f5a | ||
|
|
772c768cda | ||
|
|
a4f8f9e1cf | ||
|
|
dfae34d9b4 | ||
|
|
0bc995bd07 | ||
|
|
161e76c500 | ||
|
|
c186db4e3b | ||
|
|
652eb65082 | ||
|
|
a98ba6dc41 | ||
|
|
080ecb04d7 | ||
|
|
8de3804d4a | ||
|
|
008bd32ec1 | ||
|
|
a2f679ffc9 | ||
|
|
2d3fe64bc4 | ||
|
|
b7db4c78c4 | ||
|
|
d0fc40b483 | ||
|
|
1efae55d36 | ||
|
|
791a772199 | ||
|
|
285b896a1d | ||
|
|
098cd96655 | ||
|
|
4eee71a8fb | ||
|
|
c01bf70f62 | ||
|
|
3ed16b88a4 | ||
|
|
18f30c8286 | ||
|
|
f221a6c17f | ||
|
|
51a710ffa0 | ||
|
|
ad0c836129 | ||
|
|
be6707b461 | ||
|
|
c227ba2794 | ||
|
|
d60a770eb7 | ||
|
|
30d83a942c | ||
|
|
98bf9355dc | ||
|
|
a3928101af | ||
|
|
6e77757939 | ||
|
|
66c5d6e400 | ||
|
|
b0c184767c | ||
|
|
1165cf6e24 | ||
|
|
a9640107f3 | ||
|
|
73bc13bb7d | ||
|
|
e98966283d | ||
|
|
8ad2344ec6 | ||
|
|
b77c239f30 | ||
|
|
15cb83936b | ||
|
|
e2157ee1c0 | ||
|
|
bdfd50b970 | ||
|
|
4debfd68dc | ||
|
|
410032060a | ||
|
|
22b8fed9d9 | ||
|
|
0172587b12 | ||
|
|
f05f9e4edb | ||
|
|
24b1fa0ae3 | ||
|
|
fedb9fe74c | ||
|
|
5408da829c | ||
|
|
448e45b7e4 | ||
|
|
156f257619 | ||
|
|
7f40373fd6 | ||
|
|
f2ec03ba1b | ||
|
|
9a79c5537e | ||
|
|
cf156b0591 | ||
|
|
832ddabdf2 | ||
|
|
22080312f0 | ||
|
|
165095376d | ||
|
|
2a816b9e7b | ||
|
|
a7d03f4e25 | ||
|
|
5e18e580f7 | ||
|
|
9b7906e43d | ||
|
|
2143bbdc94 | ||
|
|
51a8810aa0 | ||
|
|
8f906026b3 | ||
|
|
aa6de3bb57 | ||
|
|
71a443e3c5 | ||
|
|
d1526d9c72 | ||
|
|
2456e35908 | ||
|
|
f359015d89 | ||
|
|
b8155f4d27 | ||
|
|
2ce903d7e7 | ||
|
|
d4765dee16 | ||
|
|
a78486d7e0 | ||
|
|
e969af9e44 | ||
|
|
e371fda1ce | ||
|
|
ee14bed3d9 | ||
|
|
3d873ed51c | ||
|
|
d85f550611 | ||
|
|
6c9daf7002 | ||
|
|
6a88e8c4f4 | ||
|
|
99246382ee | ||
|
|
73e1018f65 | ||
|
|
f1157e3be2 | ||
|
|
bd79938bed | ||
|
|
173093a403 | ||
|
|
ed61df6d8a | ||
|
|
24feb64dfe | ||
|
|
b3eaf86e40 | ||
|
|
4dfdf374af | ||
|
|
45191621d2 | ||
|
|
4d60be738e | ||
|
|
eef3bab6e4 | ||
|
|
14ebb6dc32 | ||
|
|
9e602691b6 | ||
|
|
02045001d2 | ||
|
|
3c483f5337 | ||
|
|
bcb5c3697d | ||
|
|
0e50579089 | ||
|
|
59c4cc2058 | ||
|
|
462adbe42f | ||
|
|
d708b6faa5 | ||
|
|
40d566ab8e | ||
|
|
c5d712c977 | ||
|
|
631a3da17f | ||
|
|
ff5145d7bd | ||
|
|
d1131c1a16 | ||
|
|
9b89df9ecf | ||
|
|
acff87a09f | ||
|
|
a182ce2d2c | ||
|
|
0954b5a4f2 | ||
|
|
d297223fa5 | ||
|
|
9bccb90c7a | ||
|
|
6403259e2a | ||
|
|
fa367f4128 | ||
|
|
164f281dd0 | ||
|
|
760cb31bae | ||
|
|
4f869811fe | ||
|
|
0f26a3b111 | ||
|
|
fadae2d2b7 | ||
|
|
12b00a808d | ||
|
|
f0435f5f81 | ||
|
|
f018d025a2 | ||
|
|
5d720e96e6 | ||
|
|
e1b77b0d5f | ||
|
|
2db6424f9d | ||
|
|
9a58145f72 | ||
|
|
51c1ae937c | ||
|
|
26bd9ac9aa | ||
|
|
3ba28b8156 | ||
|
|
3f318b4ac4 | ||
|
|
452c6de7e3 | ||
|
|
b1a86db58b | ||
|
|
b29eccf4bb | ||
|
|
ba28f6c62a | ||
|
|
279f671dcf | ||
|
|
954cc1bb95 | ||
|
|
776d4c9ca1 | ||
|
|
6949516e63 | ||
|
|
f6c13b0324 | ||
|
|
d811675882 | ||
|
|
735aee952a | ||
|
|
9432f4c1fa | ||
|
|
5bf5a4f86e | ||
|
|
60ef8f569b | ||
|
|
7bc59d9d26 | ||
|
|
bd4c7b7957 | ||
|
|
172b5dba8f | ||
|
|
b478ba8c45 | ||
|
|
71708b7566 | ||
|
|
c50c7406f2 | ||
|
|
00357261d5 | ||
|
|
c121908716 | ||
|
|
2191e85231 | ||
|
|
a85a964819 | ||
|
|
2ee418b6bf | ||
|
|
8f72c32fbe | ||
|
|
1cc5059179 | ||
|
|
9280a5132e | ||
|
|
a626aa6175 | ||
|
|
339742ca77 | ||
|
|
43e069eb96 | ||
|
|
8e1fecfda0 | ||
|
|
3e1c9463d6 | ||
|
|
1337811438 | ||
|
|
a028772bf4 | ||
|
|
0419e7e1c9 | ||
|
|
bdfc38d954 | ||
|
|
dbd632375c |
5
.github/uffizzi/caddy/Caddyfile
vendored
Normal file
5
.github/uffizzi/caddy/Caddyfile
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
localhost {
|
||||
bind 0.0.0.0
|
||||
reverse_proxy /ws/* http://0.0.0.0:3001
|
||||
reverse_proxy /* http://0.0.0.0:8000
|
||||
}
|
||||
53
.github/uffizzi/docker-compose.uffizzi.yml
vendored
Normal file
53
.github/uffizzi/docker-compose.uffizzi.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
version: '3.7'
|
||||
|
||||
x-uffizzi:
|
||||
ingress:
|
||||
service: windmill
|
||||
port: 8000
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: changeme
|
||||
POSTGRES_DB: windmill
|
||||
|
||||
windmill:
|
||||
image: '${WINDMILL_IMAGE}'
|
||||
ports:
|
||||
- 8000:8000
|
||||
entrypoint: ['/bin/sh', '-c']
|
||||
command: 'echo ${OAUTH_JSON_BASE64} | base64 --decode > /usr/src/app/oauth.json && ./windmill'
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:changeme@localhost/windmill?sslmode=disable
|
||||
- BASE_URL=${EXPECTED_URL}
|
||||
- BASE_INTERNAL_URL=http://localhost:8000
|
||||
- RUST_LOG=info
|
||||
- NUM_WORKERS=3
|
||||
- KEEP_JOB_DIR=false
|
||||
- DENO_PATH=/usr/bin/deno
|
||||
- PYTHON_PATH=/usr/local/bin/python3
|
||||
- METRICS_ADDR=false
|
||||
- OAUTH_JSON_BASE64=${OAUTH_JSON_BASE64}
|
||||
volumes:
|
||||
- worker_dependency_cache:/tmp/windmill/cache
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 250M
|
||||
|
||||
lsp:
|
||||
image: '${LSP_IMAGE}'
|
||||
ports:
|
||||
- 3001:3001
|
||||
|
||||
# caddy:
|
||||
# image: caddy:2.5.2-alpine
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - ./.github/uffizzi/caddy:/etc/caddy
|
||||
# environment:
|
||||
# - BASE_URL=localhost
|
||||
|
||||
volumes:
|
||||
worker_dependency_cache:
|
||||
2
.github/workflows/automerge-dependabot.yml
vendored
2
.github/workflows/automerge-dependabot.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v1.3.5
|
||||
uses: dependabot/fetch-metadata@v1.3.6
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Enable auto-merge for Dependabot PRs
|
||||
|
||||
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -40,4 +40,4 @@ jobs:
|
||||
backend -> target
|
||||
- name: cargo test
|
||||
timeout-minutes: 10
|
||||
run: mkdir frontend/build && cd backend && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill cargo test --all -- --nocapture
|
||||
run: mkdir frontend/build && cd backend && touch windmill-api/openapi-deref.yaml && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill cargo test --all -- --nocapture
|
||||
|
||||
47
.github/workflows/docker-310.yml
vendored
Normal file
47
.github/workflows/docker-310.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
name: Build and push windmill with python 3.10 and openbb
|
||||
on: workflow_dispatch
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-openbb
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build_ee:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: ./docker/DockerfileOpenbb
|
||||
build-args: |
|
||||
features=enterprise
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:openbb
|
||||
labels: |
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
106
.github/workflows/docker-image-arm.yml
vendored
106
.github/workflows/docker-image-arm.yml
vendored
@@ -1,62 +1,60 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
LOCAL_REGISTRY: registry.wimill.xyz
|
||||
# env:
|
||||
# REGISTRY: ghcr.io
|
||||
# IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
name: Build and push arm docker image
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["*"]
|
||||
# name: Build and push arm docker image
|
||||
# on:
|
||||
# push:
|
||||
# branches: [main]
|
||||
# tags: ["*"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-arm
|
||||
cancel-in-progress: true
|
||||
# concurrency:
|
||||
# group: ${{ github.ref }}-arm
|
||||
# cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish_arm:
|
||||
runs-on: [self-hosted, new]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# permissions:
|
||||
# contents: read
|
||||
# id-token: write
|
||||
# packages: write
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
# jobs:
|
||||
# publish_arm:
|
||||
# runs-on: ubuntu-22.04
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
# - uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-slim-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
# - name: Docker meta
|
||||
# id: meta-slim-public
|
||||
# uses: docker/metadata-action@v4
|
||||
# with:
|
||||
# images: |
|
||||
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# tags: |
|
||||
# type=ref,event=branch
|
||||
# type=ref,event=pr
|
||||
# type=semver,pattern={{version}}
|
||||
# type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
# - name: Login to registry
|
||||
# uses: docker/login-action@v2
|
||||
# with:
|
||||
# registry: ${{ env.REGISTRY }}
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ steps.meta-slim-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-slim-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=AGPLv3
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-arm:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-arm:buildcache,mode=max
|
||||
# - name: Build and push publicly
|
||||
# uses: depot/build-push-action@v1
|
||||
# with:
|
||||
# context: .
|
||||
# push: true
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
# tags: |
|
||||
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
# ${{ steps.meta-slim-public.outputs.tags }}
|
||||
# labels: |
|
||||
# ${{ steps.meta-slim-public.outputs.labels }}
|
||||
# org.opencontainers.image.licenses=AGPLv3
|
||||
|
||||
86
.github/workflows/docker-image.yml
vendored
86
.github/workflows/docker-image.yml
vendored
@@ -9,22 +9,27 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["*"]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, new]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
@@ -33,36 +38,8 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-slim
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push publicly
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:latest
|
||||
${{ steps.meta-slim.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-slim.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache,mode=max
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-public
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
@@ -75,47 +52,31 @@ jobs:
|
||||
|
||||
|
||||
- name: Build and push publicly
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/build-push-action@v3
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:latest
|
||||
${{ steps.meta-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=AGPLv3
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:buildcache,mode=max
|
||||
|
||||
|
||||
build_ee:
|
||||
runs-on: [self-hosted, new]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-slim
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
@@ -128,15 +89,13 @@ jobs:
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly ee
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/build-push-action@v3
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -149,8 +108,7 @@ jobs:
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-ee:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-ee:buildcache,mode=max
|
||||
|
||||
|
||||
playwright:
|
||||
runs-on: [self-hosted, new]
|
||||
@@ -172,14 +130,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Docker"
|
||||
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-slim:latest)"
|
||||
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
|
||||
id: docker-container
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: "Playwright run"
|
||||
timeout-minutes: 2
|
||||
run: cd frontend && npm ci @playwright/test && npx playwright install && npm run test
|
||||
run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=http://localhost:8000 && npm run test
|
||||
- name: "Clean up"
|
||||
run: docker kill ${{ steps.docker-container.outputs.id }}
|
||||
if: always()
|
||||
@@ -188,7 +146,6 @@ jobs:
|
||||
publish_privately_heavy:
|
||||
needs: [build_ee]
|
||||
runs-on: [self-hosted, new]
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -202,7 +159,6 @@ jobs:
|
||||
with:
|
||||
images: |
|
||||
${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
|
||||
17
.github/workflows/frontend-check.yml
vendored
Normal file
17
.github/workflows/frontend-check.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: check frontend build
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened,synchronize,reopened,closed]
|
||||
paths:
|
||||
- "frontend/**"
|
||||
jobs:
|
||||
npm_check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: "npm check"
|
||||
timeout-minutes: 2
|
||||
run: cd frontend && npm ci && npm run generate-backend-client && npm run check
|
||||
1
.github/workflows/pypi_on_release.yml
vendored
1
.github/workflows/pypi_on_release.yml
vendored
@@ -74,6 +74,7 @@ jobs:
|
||||
${{ env.ECR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ steps.metalocal.outputs.tags }}
|
||||
${{ steps.meta.outputs.tags }}
|
||||
registry.uffizzi.com/windmill-lsp:60d
|
||||
labels: ${{ steps.metalocal.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
|
||||
|
||||
93
.github/workflows/uffizzi-build.yml
vendored
Normal file
93
.github/workflows/uffizzi-build.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
name: Build PR Image
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened,synchronize,reopened,closed]
|
||||
paths:
|
||||
- "backend/**"
|
||||
- ".github/uffizzi/**"
|
||||
- ".github/workflows/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-windmill:
|
||||
name: Build and Push `windmill`
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (github.event_name != 'pull_request' || github.event.action != 'closed')}}
|
||||
outputs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Generate UUID image name
|
||||
id: uuid
|
||||
run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
|
||||
tags: type=raw,value=60d
|
||||
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
context: ./
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
|
||||
render-compose-file:
|
||||
name: Render Docker Compose File
|
||||
# Pass output of this workflow to another triggered by `workflow_run` event.
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-windmill
|
||||
outputs:
|
||||
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
- name: Render Compose File
|
||||
run: |
|
||||
WINDMILL_IMAGE=${{ needs.build-windmill.outputs.tags }}
|
||||
export WINDMILL_IMAGE
|
||||
LSP_IMAGE=registry.uffizzi.com/windmill-lsp:60d
|
||||
export LSP_IMAGE
|
||||
envsubst '${WINDMILL_IMAGE} ${LSP_IMAGE}' < ./.github/uffizzi/docker-compose.uffizzi.yml > docker-compose.rendered.yml
|
||||
cat docker-compose.rendered.yml
|
||||
- name: Upload Rendered Compose File as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: docker-compose.rendered.yml
|
||||
retention-days: 2
|
||||
- name: Serialize PR Event to File
|
||||
run: |
|
||||
cat << EOF > event.json
|
||||
${{ toJSON(github.event) }}
|
||||
EOF
|
||||
- name: Upload PR Event as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: event.json
|
||||
retention-days: 2
|
||||
|
||||
delete-preview:
|
||||
name: Call for Preview Deletion
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.action == 'closed' }}
|
||||
steps:
|
||||
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
|
||||
- name: Serialize PR Event to File
|
||||
run: echo '${{ toJSON(github.event) }}' > event.json
|
||||
- name: Upload PR Event as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: preview-spec
|
||||
path: event.json
|
||||
retention-days: 2
|
||||
115
.github/workflows/uffizzi-preview.yml
vendored
Normal file
115
.github/workflows/uffizzi-preview.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
name: Deploy Uffizzi Preview
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- "Build PR Image"
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
cache-compose-file:
|
||||
name: Cache Compose File
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
outputs:
|
||||
compose-file-cache-key: ${{ env.COMPOSE_FILE_HASH }}
|
||||
pr-number: ${{ env.PR_NUMBER }}
|
||||
steps:
|
||||
- name: 'Download artifacts'
|
||||
# Fetch output (zip archive) from the workflow run that triggered this workflow.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: context.payload.workflow_run.id,
|
||||
});
|
||||
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "preview-spec"
|
||||
})[0];
|
||||
if (matchArtifact === undefined) {
|
||||
throw TypeError('Build Artifact not found!');
|
||||
}
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));
|
||||
- name: 'Unzip artifact'
|
||||
run: unzip preview-spec.zip
|
||||
- name: Read Event into ENV
|
||||
run: |
|
||||
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
|
||||
cat event.json >> $GITHUB_ENV
|
||||
echo 'EOF' >> $GITHUB_ENV
|
||||
- name: Read PR Number From Event Object
|
||||
id: pr
|
||||
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
|
||||
- name: Predict Deployment URL
|
||||
id: url
|
||||
# Replace dots in the repo name with the plus sign
|
||||
run: |
|
||||
REPO=$(echo ${{ github.repository }} | sed 's/\./+/g')
|
||||
echo "EXPECTED_URL=https://app.uffizzi.com/github.com/$REPO/pull/$PR_NUMBER" >> $GITHUB_ENV
|
||||
|
||||
- name: Re-Render Compose File
|
||||
run: |
|
||||
OAUTH_JSON_BASE64=${{ secrets.OAUTH_JSON_BASE64 }}
|
||||
export OAUTH_JSON_BASE64
|
||||
envsubst '${OAUTH_JSON_BASE64} ${EXPECTED_URL}' < docker-compose.rendered.yml > docker-compose.uffizzi.yml
|
||||
# cat docker-compose.uffizzi.yml
|
||||
|
||||
- name: Hash Rendered Compose File
|
||||
id: hash
|
||||
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
|
||||
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
|
||||
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.uffizzi.yml | awk '{ print $1 }')" >> $GITHUB_ENV
|
||||
- name: Cache Rendered Compose File
|
||||
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: docker-compose.uffizzi.yml
|
||||
key: ${{ env.COMPOSE_FILE_HASH }}
|
||||
|
||||
- name: DEBUG - Print Job Outputs
|
||||
if: ${{ runner.debug }}
|
||||
run: |
|
||||
echo "PR number: ${{ env.PR_NUMBER }}"
|
||||
echo "Compose file hash: ${{ env.COMPOSE_FILE_HASH }}"
|
||||
cat event.json
|
||||
|
||||
deploy-uffizzi-preview:
|
||||
name: Use Remote Workflow to Preview on Uffizzi
|
||||
needs:
|
||||
- cache-compose-file
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2
|
||||
with:
|
||||
# If this workflow was triggered by a PR close event, cache-key will be an empty string
|
||||
# and this reusable workflow will delete the preview deployment.
|
||||
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
|
||||
compose-file-cache-path: docker-compose.uffizzi.yml
|
||||
server: https://app.uffizzi.com
|
||||
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
playwright:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- deploy-uffizzi-preview
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: "Playwright run"
|
||||
timeout-minutes: 2
|
||||
run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=${{ needs.deploy-uffizzi-preview.outputs.url }} && npm run test
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.analysis.typeCheckingMode": "basic"
|
||||
}
|
||||
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,6 +1,71 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.61.1](https://github.com/windmill-labs/windmill/compare/v1.61.0...v1.61.1) (2023-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** compile issue ([df8cc1f](https://github.com/windmill-labs/windmill/commit/df8cc1f2482b3d8b1530cdaef1361303ff5cadff))
|
||||
|
||||
## [1.61.0](https://github.com/windmill-labs/windmill/compare/v1.60.0...v1.61.0) (2023-01-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add openapi viewer ([#1094](https://github.com/windmill-labs/windmill/issues/1094)) ([1337811](https://github.com/windmill-labs/windmill/commit/1337811438d48e23133f68e9157bd185d5fe4a82))
|
||||
* add PIP_LOCAL_DEPENDENCIES ([b7db4c7](https://github.com/windmill-labs/windmill/commit/b7db4c78c4629f1fd2dfd7a338f783b16f07b24d))
|
||||
* add QUEUE_LIMIT_WAIT_RESULT ([51a8810](https://github.com/windmill-labs/windmill/commit/51a8810aa0a9ab7702df459dd270278d42bd3899))
|
||||
* add resource and resource type from json ([080ecb0](https://github.com/windmill-labs/windmill/commit/080ecb04d7a08d035fe07f179975b52bc0f77297))
|
||||
* add sql as a valid type in Python ([0172587](https://github.com/windmill-labs/windmill/commit/0172587b129ce54d96dc99336a1f56c66ebdbef5))
|
||||
* add sync webhook for flows ([f377c84](https://github.com/windmill-labs/windmill/commit/f377c84f5a2148a2bbb7c16e93f13e1d85ceb17e))
|
||||
* **backend:** add queue_limit + configurable timeout + fix timeout cancel ([eef3bab](https://github.com/windmill-labs/windmill/commit/eef3bab6e4d9f1af1435db868c707a692558ab74))
|
||||
* **deno:** add support for DENO_AUTH_TOKENS ([832ddab](https://github.com/windmill-labs/windmill/commit/832ddabdf2239521368e5f96df144abce0db31c2))
|
||||
* **deno:** allow overriding deno sandboxing with DENO_FLAGS' ([7f40373](https://github.com/windmill-labs/windmill/commit/7f40373fd64005d87972854a565c6cf521232982))
|
||||
* **frontend:** Add app inputs configurations ([#1142](https://github.com/windmill-labs/windmill/issues/1142)) ([3ed16b8](https://github.com/windmill-labs/windmill/commit/3ed16b88a42e4db6e12f8557c5bbaa2d832b1c17))
|
||||
* **frontend:** Add app preview lock ([#1127](https://github.com/windmill-labs/windmill/issues/1127)) ([6a88e8c](https://github.com/windmill-labs/windmill/commit/6a88e8c4f4d6fa5c393ce27b2040784a74a73b06))
|
||||
* **frontend:** Add copy button option to app text display component ([#1090](https://github.com/windmill-labs/windmill/issues/1090)) ([bdfc38d](https://github.com/windmill-labs/windmill/commit/bdfc38d954a3c5548fb7f9ee6f80f741eff8cb67))
|
||||
* **frontend:** Add default codes to app editor ([#1099](https://github.com/windmill-labs/windmill/issues/1099)) ([c50c740](https://github.com/windmill-labs/windmill/commit/c50c7406f267b480af2a01b47e3fcfa1d763db7a))
|
||||
* **frontend:** Add HTML result rendering ([#1160](https://github.com/windmill-labs/windmill/issues/1160)) ([c01bf70](https://github.com/windmill-labs/windmill/commit/c01bf70f62680a4b77812ac6eb64aca2b15d9a8d))
|
||||
* **frontend:** Add more integration icons ([#1097](https://github.com/windmill-labs/windmill/issues/1097)) ([2191e85](https://github.com/windmill-labs/windmill/commit/2191e852318f069489f77a4f1c44aadf248c7f53))
|
||||
* **frontend:** add plotly support ([a4f8f9e](https://github.com/windmill-labs/windmill/commit/a4f8f9e1cf80395d5cd1229c8dd5dda244e2ba7f))
|
||||
* **frontend:** add selectedRowIndex to the table outputs ([#1145](https://github.com/windmill-labs/windmill/issues/1145)) ([f05f9e4](https://github.com/windmill-labs/windmill/commit/f05f9e4edb928e7a8e3e66a62de9c6487684a14b))
|
||||
* **frontend:** Add Supabase resource ([#1107](https://github.com/windmill-labs/windmill/issues/1107)) ([12b00a8](https://github.com/windmill-labs/windmill/commit/12b00a808d1f12827a7bc26518cc6f972bdde917))
|
||||
* **frontend:** add support for background scripts + add FormButtonCo… ([#1124](https://github.com/windmill-labs/windmill/issues/1124)) ([e969af9](https://github.com/windmill-labs/windmill/commit/e969af9e44d1b4409064080e8662552ee3e262e8))
|
||||
* **frontend:** Add surreal db logo ([#1102](https://github.com/windmill-labs/windmill/issues/1102)) ([d811675](https://github.com/windmill-labs/windmill/commit/d81167588227f2cc433aab64551d96d21a589c5b))
|
||||
* **frontend:** Add tooltip to app recompute ([#1122](https://github.com/windmill-labs/windmill/issues/1122)) ([4dfdf37](https://github.com/windmill-labs/windmill/commit/4dfdf374af358ef46ee8057373546719c6570067))
|
||||
* **frontend:** add vega-lite component ([bd79938](https://github.com/windmill-labs/windmill/commit/bd79938bed6da3875a4a2dd72dad14dedbf25ddf))
|
||||
* **frontend:** Display error as an icon in order to avoid clutter wh… ([#1143](https://github.com/windmill-labs/windmill/issues/1143)) ([22b8fed](https://github.com/windmill-labs/windmill/commit/22b8fed9d904a37aae66f6d957f4987f6ca9955c))
|
||||
* **frontend:** Open debug runs from component ([#1155](https://github.com/windmill-labs/windmill/issues/1155)) ([73bc13b](https://github.com/windmill-labs/windmill/commit/73bc13bb7d4b1eb25a3a726ac9e6bb80120a495f))
|
||||
* **frontend:** Update app table component styles ([#1100](https://github.com/windmill-labs/windmill/issues/1100)) ([172b5db](https://github.com/windmill-labs/windmill/commit/172b5dba8f4c3aaf11569c72313ad74845c668a6))
|
||||
* **python:** add support for extra args in python ([772c768](https://github.com/windmill-labs/windmill/commit/772c768cda094f208a5efb7aab03eee3a8f38f68))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** Add default value for text, number and date input + fix issues with number input + add date input in the settings panel ([#1135](https://github.com/windmill-labs/windmill/issues/1135)) ([8f90602](https://github.com/windmill-labs/windmill/commit/8f906026b3203702c3b6a30bcac9fb2aca985c29))
|
||||
* **frontend:** Add highlight to selected workspace ([#1159](https://github.com/windmill-labs/windmill/issues/1159)) ([f221a6c](https://github.com/windmill-labs/windmill/commit/f221a6c17f145d0c42f7faf785c37f4037308973))
|
||||
* **frontend:** add missing condition to properly select first row ([#1128](https://github.com/windmill-labs/windmill/issues/1128)) ([3d873ed](https://github.com/windmill-labs/windmill/commit/3d873ed51c769005981a8d8dfb95faa3ca33bb83))
|
||||
* **frontend:** App form component display ([#1096](https://github.com/windmill-labs/windmill/issues/1096)) ([339742c](https://github.com/windmill-labs/windmill/commit/339742ca77dd0fda19d5a262617e42c341ef5871))
|
||||
* **frontend:** App script list panel overflow ([#1101](https://github.com/windmill-labs/windmill/issues/1101)) ([7bc59d9](https://github.com/windmill-labs/windmill/commit/7bc59d9d2650b623a2b481a727ffc495b4216f22))
|
||||
* **frontend:** App table action button cell ([#1149](https://github.com/windmill-labs/windmill/issues/1149)) ([e989662](https://github.com/windmill-labs/windmill/commit/e98966283dd9b57cc07da34876a90d19210c2927))
|
||||
* **frontend:** App table header z-index ([#1120](https://github.com/windmill-labs/windmill/issues/1120)) ([59c4cc2](https://github.com/windmill-labs/windmill/commit/59c4cc2058f86deea793b61de59e2936e50e5577))
|
||||
* **frontend:** Check if hiddenInlineScripts are undefined before iterating over them ([#1134](https://github.com/windmill-labs/windmill/issues/1134)) ([71a443e](https://github.com/windmill-labs/windmill/commit/71a443e3c56d2b8c951de6e3701a411ad1a0ce34))
|
||||
* **frontend:** fix first row selection ([#1125](https://github.com/windmill-labs/windmill/issues/1125)) ([6c9daf7](https://github.com/windmill-labs/windmill/commit/6c9daf70021859dcd7cef717bc3acdfa88cffd02))
|
||||
* **frontend:** Fix id generation when a second action ([#1110](https://github.com/windmill-labs/windmill/issues/1110)) ([4f86981](https://github.com/windmill-labs/windmill/commit/4f869811fee73826b2b10965241d2d8dba59dc2a))
|
||||
* **frontend:** Make sure AppSelect items are an array ([#1144](https://github.com/windmill-labs/windmill/issues/1144)) ([24b1fa0](https://github.com/windmill-labs/windmill/commit/24b1fa0ae327c984841f9ed8b163b3fccc6da258))
|
||||
* **frontend:** Make sure that old apps are rendering properly ([#1132](https://github.com/windmill-labs/windmill/issues/1132)) ([a78486d](https://github.com/windmill-labs/windmill/commit/a78486d7e08f76e22406063288b35e9030974d7a))
|
||||
* **frontend:** Playwright ([#1108](https://github.com/windmill-labs/windmill/issues/1108)) ([f0435f5](https://github.com/windmill-labs/windmill/commit/f0435f5f81941c5b49500003aa27956d627daadb))
|
||||
* **frontend:** Prepare app scripts code for export ([#1123](https://github.com/windmill-labs/windmill/issues/1123)) ([173093a](https://github.com/windmill-labs/windmill/commit/173093a40321f6ad35bf766a5554b21cea388771))
|
||||
* **frontend:** Prevent modal from hijacking all keypress event ([#1136](https://github.com/windmill-labs/windmill/issues/1136)) ([aa6de3b](https://github.com/windmill-labs/windmill/commit/aa6de3bb5746b9d99c8e3a52e6a9fff10d97bc6a))
|
||||
* **frontend:** Revert component input panel change ([#1092](https://github.com/windmill-labs/windmill/issues/1092)) ([0419e7e](https://github.com/windmill-labs/windmill/commit/0419e7e1c9239fd3cbc49acf82a73e9c01938153))
|
||||
* **frontend:** Runnable table overflow ([#1119](https://github.com/windmill-labs/windmill/issues/1119)) ([462adbe](https://github.com/windmill-labs/windmill/commit/462adbe42f823646413a5003fd71f3dd473c0728))
|
||||
* **frontend:** Select the first row by default, and remove the abilit… ([#1121](https://github.com/windmill-labs/windmill/issues/1121)) ([3c483f5](https://github.com/windmill-labs/windmill/commit/3c483f533759b9b4e589055dbddb31f294bea8fa))
|
||||
* **frontend:** Show app builder header always on top ([#1118](https://github.com/windmill-labs/windmill/issues/1118)) ([631a3da](https://github.com/windmill-labs/windmill/commit/631a3da17f05a3d29defdf96a50d7e96a9f8baad))
|
||||
* **frontend:** Update app scripts pane ([#1146](https://github.com/windmill-labs/windmill/issues/1146)) ([18f30c8](https://github.com/windmill-labs/windmill/commit/18f30c8286f8240158643ade8b0ef4607a80fbb0))
|
||||
* **frontend:** Use absolute path on connect images ([#1095](https://github.com/windmill-labs/windmill/issues/1095)) ([43e069e](https://github.com/windmill-labs/windmill/commit/43e069eb96c0af7d3a1fe1db4f4b69f8e31e7438))
|
||||
* improvements for error handling as first step of flow ([b77c239](https://github.com/windmill-labs/windmill/commit/b77c239f307a37777acb083b0cdb5c0d214a9dd8))
|
||||
|
||||
## [1.60.0](https://github.com/windmill-labs/windmill/compare/v1.59.0...v1.60.0) (2023-01-11)
|
||||
|
||||
|
||||
|
||||
@@ -51,12 +51,16 @@ COPY frontend .
|
||||
RUN mkdir /backend
|
||||
COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml
|
||||
COPY /openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh
|
||||
RUN cd /backend/windmill-api && . ./build_openapi.sh
|
||||
|
||||
RUN npm run generate-backend-client
|
||||
ENV NODE_OPTIONS "--max-old-space-size=8192"
|
||||
RUN npm run build
|
||||
RUN npm run check
|
||||
|
||||
|
||||
|
||||
FROM rust_base AS planner
|
||||
|
||||
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
|
||||
@@ -75,6 +79,7 @@ COPY ./openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY ./backend ./
|
||||
|
||||
COPY --from=frontend /frontend /frontend
|
||||
COPY --from=frontend /backend/windmill-api/openapi-deref.yaml ./windmill-api/openapi-deref.yaml
|
||||
COPY .git/ .git/
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"
|
||||
|
||||
12
README.md
12
README.md
@@ -91,7 +91,7 @@ Windmill is <b>fully open-sourced (AGPLv3)</b>:
|
||||
shared on [WindmillHub](https://hub.windmill.dev).
|
||||

|
||||
|
||||
4. (Coming soon) Build complex UI on top of your scripts and flows.
|
||||
4. Build complex UI on top of your scripts and flows.
|
||||

|
||||
|
||||
Scripts and flows can also be triggered by a cron schedule '*/5 * * * *' or
|
||||
@@ -285,6 +285,7 @@ upcoming CLI tool.
|
||||
| BASE_INTERNAL_URL | http://localhost:8000 | The base url that is reachable by your workers to talk to the Servers. This help avoiding going through the external load balancer for VPC-internal requests. | Worker |
|
||||
| TIMEOUT | 300 | The timeout in seconds for the execution of a script | Worker |
|
||||
| SLEEP_QUEUE | 50 | The number of ms to sleep in between the last check for new jobs in the DB. It is multiplied by NUM_WORKERS such that in average, for one worker instance, there is one pull every SLEEP_QUEUE ms. | Worker |
|
||||
| MAX_LOG_SIZE | 500000 | The maximum number of characters a job can emit (log + result) | Worker |
|
||||
| DISABLE_NUSER | false | If Nsjail is enabled, disable the nsjail's `clone_newuser` setting | Worker |
|
||||
| KEEP_JOB_DIR | false | Keep the job directory after the job is done. Useful for debugging. | Worker |
|
||||
| LICENSE_KEY (EE only) | None | License key checked at startup for the Enterprise Edition of Windmill | Worker |
|
||||
@@ -303,6 +304,15 @@ upcoming CLI tool.
|
||||
| HOME | None | The home directory to use for Go and Bash , usually inherited | Worker |
|
||||
| DATABASE_CONNECTIONS | 50 (Server)/3 (Worker) | The max number of connections in the database connection pool | All |
|
||||
| SUPERADMIN_SECRET | None | A token that would let the caller act as a virtual superadmin superadmin@windmill.dev | Server |
|
||||
| TIMEOUT_WAIT_RESULT | 20 | The number of seconds to wait before timeout on the 'run_wait_result' endpoint | Worker |
|
||||
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
|
||||
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
|
||||
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
|
||||
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again |
|
||||
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
|
||||
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |
|
||||
|
||||
|
||||
|
||||
|
||||
## Run a local dev setup
|
||||
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
target/
|
||||
.env
|
||||
oauth.json
|
||||
windmill-api/openapi-deref.yaml
|
||||
842
backend/Cargo.lock
generated
842
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.60.0"
|
||||
version = "1.61.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.60.0"
|
||||
version = "1.61.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -45,6 +45,9 @@ tokio-metrics.workspace = true
|
||||
rand.workspace = true
|
||||
chrono.workspace = true
|
||||
git-version.workspace = true
|
||||
rsa.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
@@ -80,7 +83,7 @@ chrono = { version = "^0", features = ["serde"] }
|
||||
tracing = "^0"
|
||||
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
|
||||
prometheus = { version = "^0", default-features = false }
|
||||
cookie = { version = "^0" }
|
||||
cookie = { version = "0.16.2" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
rust-embed = "^6"
|
||||
mime_guess = "^2"
|
||||
@@ -140,3 +143,5 @@ lazy_static = "1.4.0"
|
||||
serde_derive = "1.0.147"
|
||||
const_format = { version = "0.2", features = ["rust_1_64", "rust_1_51"] }
|
||||
dyn-iter = "0.2.0"
|
||||
rsa = "0.7.2"
|
||||
async-stripe = { version = "0.14", features = ["runtime-tokio-hyper", "checkout"] }
|
||||
1
backend/migrations/20230119194229_customer_id.down.sql
Normal file
1
backend/migrations/20230119194229_customer_id.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
3
backend/migrations/20230119194229_customer_id.up.sql
Normal file
3
backend/migrations/20230119194229_customer_id.up.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE workspace_settings ADD COLUMN customer_id VARCHAR(100);
|
||||
ALTER TABLE workspace_settings ADD COLUMN plan VARCHAR(40);
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE account ALTER COLUMN refresh_token TYPE VARCHAR(1500);
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
|
||||
GRANT ALL PRIVILEGES ON TABLE favorite TO windmill_admin;
|
||||
GRANT ALL PRIVILEGES ON TABLE favorite TO windmill_user;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,25 @@
|
||||
-- Add up migration script here
|
||||
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user ON capture FOR ALL
|
||||
USING (SPLIT_PART(capture.path, '/', 1) = 'f' AND SPLIT_PART(capture.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]))
|
||||
WITH CHECK (SPLIT_PART(capture.path, '/', 1) = 'f' AND SPLIT_PART(capture.path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
|
||||
DO
|
||||
$do$
|
||||
DECLARE
|
||||
i text;
|
||||
arr text[] := array['queue', 'completed_job'];
|
||||
BEGIN
|
||||
FOREACH i IN ARRAY arr
|
||||
LOOP
|
||||
EXECUTE FORMAT(
|
||||
$$
|
||||
CREATE POLICY see_folder_extra_perms_user ON %1$I FOR ALL
|
||||
USING (%1$I.visible_to_owner IS true AND SPLIT_PART(%1$I.script_path, '/', 1) = 'f' AND SPLIT_PART(%1$I.script_path, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]));
|
||||
$$,
|
||||
i
|
||||
);
|
||||
END LOOP;
|
||||
END
|
||||
$do$;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Add up migration script here
|
||||
|
||||
CREATE POLICY see_folder_extra_perms_user ON account FOR ALL
|
||||
USING (SPLIT_PART(account.owner, '/', 1) = 'f' AND SPLIT_PART(account.owner, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_read'), ',')::text[]))
|
||||
WITH CHECK (SPLIT_PART(account.owner, '/', 1) = 'f' AND SPLIT_PART(account.owner, '/', 2) = any(regexp_split_to_array(current_setting('session.folders_write'), ',')::text[]));
|
||||
@@ -80,7 +80,7 @@ pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
let def_arg_start = params.args.len() - params.defaults.len();
|
||||
Ok(MainArgSignature {
|
||||
star_args: params.vararg.is_some(),
|
||||
star_kwargs: params.vararg.is_some(),
|
||||
star_kwargs: params.kwarg.is_some(),
|
||||
args: params
|
||||
.args
|
||||
.into_iter()
|
||||
@@ -104,6 +104,7 @@ pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
"bytes" => Typ::Bytes,
|
||||
"datetime" => Typ::Datetime,
|
||||
"datetime.datetime" => Typ::Datetime,
|
||||
"Sql" | "sql" => Typ::Sql,
|
||||
_ => Typ::Resource(id),
|
||||
},
|
||||
_ => Typ::Unknown,
|
||||
@@ -135,7 +136,10 @@ fn to_value(et: &ExprKind) -> Option<serde_json::Value> {
|
||||
.into_iter()
|
||||
.zip(values)
|
||||
.map(|(k, v)| {
|
||||
let key = to_value(&k.node)
|
||||
let key = k
|
||||
.as_ref()
|
||||
.map(|x| x.node.clone())
|
||||
.and_then(|n| to_value(&n))
|
||||
.and_then(|x| match x {
|
||||
serde_json::Value::String(s) => Some(s),
|
||||
_ => None,
|
||||
@@ -174,10 +178,14 @@ fn constant_to_value(c: &Constant) -> serde_json::Value {
|
||||
|
||||
static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_map! {
|
||||
"psycopg2" => "psycopg2-binary",
|
||||
"psycopg" => "psycopg[binary, pool]",
|
||||
"yaml" => "pyyaml",
|
||||
"git" => "GitPython",
|
||||
"u" => "requests",
|
||||
"f" => "requests"
|
||||
"f" => "requests",
|
||||
"shopify" => "ShopifyAPI",
|
||||
"seleniumwire" => "selenium-wire",
|
||||
"openbb-terminal" => "openbb[all]",
|
||||
};
|
||||
|
||||
fn replace_import(x: String) -> String {
|
||||
@@ -189,13 +197,13 @@ fn replace_import(x: String) -> String {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r"^\#(\S+)$").unwrap();
|
||||
static ref RE: Regex = Regex::new(r"^\#\s?(\S+)$").unwrap();
|
||||
}
|
||||
|
||||
pub fn parse_python_imports(code: &str) -> error::Result<Vec<String>> {
|
||||
let find_requirements = code
|
||||
.lines()
|
||||
.find_position(|x| x.starts_with("#requirements:"));
|
||||
.find_position(|x| x.starts_with("#requirements:") || x.starts_with("# requirements:"));
|
||||
if let Some((pos, _)) = find_requirements {
|
||||
let lines = code
|
||||
.lines()
|
||||
|
||||
@@ -548,6 +548,16 @@
|
||||
"name": "auto_invite_operator",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "customer_id",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "plan",
|
||||
"ordinal": 8,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -557,6 +567,8 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
@@ -1576,6 +1588,20 @@
|
||||
},
|
||||
"query": "SELECT substr(logs, $1) as logs FROM completed_job WHERE workspace_id = $2 AND id = $3"
|
||||
},
|
||||
"460a3c3161899e172d2d20004e12ad3f331c73f260d099749a9e8fb85ae5614e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE queue SET canceled = true, canceled_reason = 'http connection broke', canceled_by = $1 WHERE id = $2 AND workspace_id = $3"
|
||||
},
|
||||
"46b44de4ea6eb15258c1cb9bbd21cab7fc350b46e8899d59759945eae02a6d40": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1745,23 +1771,6 @@
|
||||
},
|
||||
"query": "\n UPDATE queue\n SET flow_status = JSONB_SET(\n JSONB_SET(flow_status, ARRAY['modules', $1::TEXT], $2),\n ARRAY['step'], $3)\n WHERE id = $4\n "
|
||||
},
|
||||
"53f2836a652d34aafe6ffb266a092d7390212bc5532dcc226529a61a53deec42": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)"
|
||||
},
|
||||
"541ebd3bac65431237cf3b882dfdcd61ca97c253d9754d05bba59fda89841067": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1831,6 +1840,16 @@
|
||||
"name": "auto_invite_operator",
|
||||
"ordinal": 6,
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"name": "customer_id",
|
||||
"ordinal": 7,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "plan",
|
||||
"ordinal": 8,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -1840,6 +1859,8 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
@@ -2037,73 +2058,6 @@
|
||||
},
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM app WHERE path = $1 AND workspace_id = $2)"
|
||||
},
|
||||
"59fc51efa01e823ec63f55a8081f282db0d7a40716c04f4043925eb84fe4429b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Numeric",
|
||||
"Varchar",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, EXTRACT(milliseconds FROM (now() - $6))), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
|
||||
},
|
||||
"5b9b58612ca0f703a5d154a76fab82ac2329aef965fa937bfab2810b6e1336a4": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -2117,6 +2071,26 @@
|
||||
},
|
||||
"query": "DELETE FROM group_ WHERE name = $1 AND workspace_id = $2"
|
||||
},
|
||||
"5ba4b87528ad49f17d72b53c3db30f5ca4b3b0b0afbd5d9721c8b5d692af601b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT COUNT(id) FROM queue WHERE email = $1"
|
||||
},
|
||||
"5c377fffc224a06f693c125f4c13b0a9ccfc217190ba6cf78246294bbc6c93bc": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -3826,6 +3800,73 @@
|
||||
},
|
||||
"query": "UPDATE flow SET dependency_job = $1 WHERE path = $2 AND workspace_id = $3"
|
||||
},
|
||||
"a46d97b0c65bbbcc29022c2d010a4853541987a58f0098f9c2e3fd6e40b11a52": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Numeric",
|
||||
"Varchar",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
|
||||
},
|
||||
"a6145b0482c9e5da245059a80b1563cad20318fd2dd8aef33f9ca97de1826b8b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -4063,6 +4104,26 @@
|
||||
},
|
||||
"query": "\n SELECT resume_id, approver\n FROM resume_job\n WHERE job = $1\n "
|
||||
},
|
||||
"b053117536c067095e2fb2864ce5af33f84b22c24c92fcb870f37f0501f8ea9a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT COUNT(id) FROM queue WHERE running = true AND email = $1"
|
||||
},
|
||||
"b05c5f62ef4aa21d33369130cced0e9d7d128727eb58a9be7ae69cbb16bcbb27": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -4191,6 +4252,24 @@
|
||||
},
|
||||
"query": "SELECT count(path) FROM schedule WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2"
|
||||
},
|
||||
"b50b8d3367bd1f74986e6f923fe8497bcbfffea2570a3f3d1f8100207bc1557c": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "count",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
}
|
||||
},
|
||||
"query": "SELECT COUNT(*) FROM queue WHERE canceled = false AND (scheduled_for <= now()\n OR (suspend_until IS NOT NULL\n AND ( suspend <= 0\n OR suspend_until <= now())))"
|
||||
},
|
||||
"b5c9891b5bf3d581e62f8835aaa25b8158fe3cefc849af0ba312a45cf22721ca": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -4532,6 +4611,26 @@
|
||||
},
|
||||
"query": "UPDATE schedule SET script_path = $1 WHERE script_path = $2 AND workspace_id = $3 AND is_flow IS false RETURNING *"
|
||||
},
|
||||
"c07c9276945663d062cf0ff5b3323be681a0e2cb07a457ea9aede2daeff551cc": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "customer_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT customer_id FROM workspace_settings WHERE workspace_id = $1"
|
||||
},
|
||||
"c1149f74ef0cca4a48be80f523d339d78e8e20e2bfacac9ad1b6b75c5ae370fb": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -5524,6 +5623,24 @@
|
||||
},
|
||||
"query": "DELETE FROM password WHERE email = $1"
|
||||
},
|
||||
"e9c0e331c16312bf086b17c91466c5389d41454fd3f18d73c2e9554845ee9a72": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO token\n (workspace_id, token, owner, label, expiration, super_admin, email)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6, $7)"
|
||||
},
|
||||
"ea8ebb8d972fe99c960b5a69f794ee2b57bfb1914bf370c5b10313e45fa9b65f": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
||||
24
backend/src/ee.rs
Normal file
24
backend/src/ee.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
#[cfg(feature = "enterprise")]
|
||||
use rsa::{pkcs8::DecodePublicKey, signature::Verifier};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use sha2::Sha256;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub fn verify_license_key(license_key: Option<String>) -> anyhow::Result<()> {
|
||||
if let Some(license_key) = license_key {
|
||||
let splitted_lk = license_key
|
||||
.split_once(".")
|
||||
.expect("license_key can be splitted with a .");
|
||||
|
||||
let pub_key = rsa::RsaPublicKey::from_public_key_der(
|
||||
&base64::decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgVShzcLSPiOi+8ET8fggob1kmi47/cE12JaidPkwfGnScZItghkqtiLsct0U4kJhlp5gO89DYTBmIKadvxwY7kMsLlZzmi2emVH7c27cByGASY8QmWDNdG4Ggy/NDflGGBdAtN6gHawZAg4zHv3qpbPQGHH1/6sXIohcXhOnouwIDAQAB")?)?;
|
||||
let msg = base64::decode(splitted_lk.0)?;
|
||||
let signature = base64::decode(splitted_lk.1)?;
|
||||
rsa::pss::VerifyingKey::<Sha256>::new(pub_key)
|
||||
.verify(&msg, &rsa::pss::Signature::from(signature))
|
||||
.map_err(|_| anyhow::anyhow!("Invalid license key".to_string()))?;
|
||||
} else {
|
||||
panic!("License key is required for the enterprise edition");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -15,6 +15,8 @@ use windmill_worker::WorkerConfig;
|
||||
|
||||
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
|
||||
mod ee;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenv::dotenv().ok();
|
||||
@@ -190,19 +192,12 @@ pub async fn run_workers(
|
||||
license_key: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "enterprise")]
|
||||
if let Some(license_key) = license_key {
|
||||
if license_key != "REQUIRED_DEC1" {
|
||||
panic!("Invalid license key");
|
||||
}
|
||||
} else {
|
||||
panic!("License key is required for the enterprise edition");
|
||||
}
|
||||
ee::verify_license_key(license_key)?;
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
if license_key.is_some() {
|
||||
panic!("License key is required ONLY for the enterprise edition");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
if !worker_config.disable_nsjail {
|
||||
tracing::warn!(
|
||||
|
||||
@@ -186,7 +186,7 @@ mod suspend_resume {
|
||||
const secret = await r.text();\
|
||||
console.log('Secret: ' + secret + ' ' + job + ' ' + token);\
|
||||
const r2 = await fetch(
|
||||
`http://localhost:${port}/api/w/test-workspace/jobs/${op}/${job}/0/${secret}?approver=ruben`,\
|
||||
`http://localhost:${port}/api/w/test-workspace/jobs_u/${op}/${job}/0/${secret}?approver=ruben`,\
|
||||
{\
|
||||
method: 'POST',\
|
||||
body: JSON.stringify('from job'),\
|
||||
@@ -276,7 +276,7 @@ mod suspend_resume {
|
||||
|
||||
/* ImZyb20gdGVzdCIK = base64 "from test" */
|
||||
reqwest::get(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/resume/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK&approver=ruben"
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs_u/resume/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK&approver=ruben"
|
||||
))
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -333,7 +333,7 @@ mod suspend_resume {
|
||||
server.close().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
json!({"error": "Job canceled: approval request disapproved by ruben" }),
|
||||
json!({"error": {"name": "Canceled", "reason": "approval request disapproved", "message": "Job canceled: approval request disapproved by ruben", "canceler": "ruben"}}),
|
||||
result
|
||||
);
|
||||
}
|
||||
@@ -379,7 +379,7 @@ mod suspend_resume {
|
||||
|
||||
/* ImZyb20gdGVzdCIK = base64 "from test" */
|
||||
reqwest::get(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/cancel/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK"
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs_u/cancel/{second}/0/{secret}?payload=ImZyb20gdGVzdCIK"
|
||||
))
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -395,7 +395,7 @@ mod suspend_resume {
|
||||
let result = completed_job(flow, &db).await.result.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
json!({"error": "Job canceled: approval request disapproved by unknown" }),
|
||||
json!( {"error": {"name": "Canceled", "reason": "approval request disapproved", "message": "Job canceled: approval request disapproved by unknown", "canceler": "unknown"}}),
|
||||
result
|
||||
);
|
||||
}
|
||||
@@ -462,7 +462,7 @@ export async function main(index, port) {
|
||||
const buf = new Uint8Array([0]);
|
||||
const sock = await Deno.connect({ port });
|
||||
await sock.write(new Uint8Array([index]));
|
||||
if (await sock.read(buf) != 1) throw "read";
|
||||
if (await sock.read(buf) != 1) throw Error("read");
|
||||
return buf[0];
|
||||
}
|
||||
"#
|
||||
@@ -581,10 +581,17 @@ def main(last, port):
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(server.close().await, attempts);
|
||||
assert!(result[1]["error"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains(r#"Uncaught (in promise) "read""#));
|
||||
|
||||
assert!(
|
||||
result[1]["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
== "read"
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
@@ -615,6 +622,10 @@ def main(last, port):
|
||||
let result = job.result.unwrap();
|
||||
assert_eq!(server.close().await, attempts);
|
||||
assert!(result["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("index out of range"));
|
||||
@@ -684,9 +695,7 @@ def main(error, port):
|
||||
result,
|
||||
json!({
|
||||
"recv": 42,
|
||||
"from failure module": {
|
||||
"error": "Error during execution of the script:\n\nTraceback (most recent call last):\n File \"/tmp/main.py\", line 15, in <module>\n res = inner_script.main(**kwargs)\n File \"/tmp/inner.py\", line 5, in main\n return sock.recv(1)[0]\nIndexError: index out of range",
|
||||
}
|
||||
"from failure module": {"error": {"name": "IndexError", "stack": " File \"/tmp/inner.py\", line 5, in main\n return sock.recv(1)[0]\n", "message": "index out of range"}},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -739,9 +748,13 @@ async fn test_iteration(db: Pool<Postgres>) {
|
||||
.unwrap();
|
||||
assert!(matches!(result, serde_json::Value::Array(_)));
|
||||
assert!(result[2]["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("StopIteration: 2"));
|
||||
.contains("2"));
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
@@ -792,9 +805,13 @@ async fn test_iteration_parallel(db: Pool<Postgres>) {
|
||||
let result = job.result.unwrap();
|
||||
assert!(matches!(result, serde_json::Value::Array(_)));
|
||||
assert!(result[2]["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("StopIteration: 2"));
|
||||
.contains("2"));
|
||||
}
|
||||
|
||||
struct RunJob {
|
||||
@@ -1767,7 +1784,7 @@ async fn test_invalid_first_step(db: Pool<Postgres>) {
|
||||
|
||||
assert_eq!(
|
||||
job.result.unwrap(),
|
||||
serde_json::json!({"error":"Expected an array value, found: {}"})
|
||||
serde_json::json!( {"error": {"name": "InternalErr", "message": "Expected an array value, found: {}"}})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2083,7 +2100,7 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!([{"error": "Error during execution of the script:\n\nerror: Uncaught (in promise) Error: failure\nexport function main(){ throw Error('failure') }\n ^\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1"}, [1,3]])
|
||||
serde_json::json!([{"error": {"name": "Error", "stack": "Error: failure\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1", "message": "failure"}}, [1,3]])
|
||||
);
|
||||
|
||||
let flow: FlowValue = serde_json::from_value(json!({
|
||||
@@ -2117,7 +2134,7 @@ async fn test_branchall_skip_failure(db: Pool<Postgres>) {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!([{"error": "Error during execution of the script:\n\nerror: Uncaught (in promise) Error: failure\nexport function main(){ throw Error('failure') }\n ^\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1"}, [1, 2]])
|
||||
serde_json::json!([ {"error": {"name": "Error", "stack": "Error: failure\n at main (file:///tmp/inner.ts:1:31)\n at run (file:///tmp/main.ts:9:26)\n at file:///tmp/main.ts:14:1", "message": "failure"}}, [1, 2]])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2256,7 +2273,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
},
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"content": "export function main(n, l) { if (n == 0) throw l; return { l: [...l, 0] } }",
|
||||
"content": "export function main(n, l) { if (n == 0) throw Error(JSON.stringify(l)); return { l: [...l, 0] } }",
|
||||
},
|
||||
}, {
|
||||
"id": "b",
|
||||
@@ -2267,7 +2284,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
},
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"content": "export function main(n, l) { if (n == 1) throw l; return { l: [...l, 1] } }",
|
||||
"content": "export function main(n, l) { if (n == 1) throw Error(JSON.stringify(l)); return { l: [...l, 1] } }",
|
||||
},
|
||||
}, {
|
||||
"value": {
|
||||
@@ -2277,7 +2294,7 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
},
|
||||
"type": "rawscript",
|
||||
"language": "deno",
|
||||
"content": "export function main(n, l) { if (n == 2) throw l; return { l: [...l, 2] } }",
|
||||
"content": "export function main(n, l) { if (n == 2) throw Error(JSON.stringify(l)); return { l: [...l, 2] } }",
|
||||
},
|
||||
}],
|
||||
"failure_module": {
|
||||
@@ -2297,10 +2314,15 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
.await
|
||||
.result
|
||||
.unwrap();
|
||||
|
||||
assert!(result["from failure module"]["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("Uncaught (in promise) []"));
|
||||
.contains("[]"));
|
||||
|
||||
let result = RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None })
|
||||
.arg("n", json!(1))
|
||||
@@ -2308,10 +2330,15 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
.await
|
||||
.result
|
||||
.unwrap();
|
||||
|
||||
assert!(result["from failure module"]["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("Uncaught (in promise) [ 0 ]"));
|
||||
.contains("[0]"));
|
||||
|
||||
let result = RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None })
|
||||
.arg("n", json!(2))
|
||||
@@ -2319,10 +2346,15 @@ async fn test_failure_module(db: Pool<Postgres>) {
|
||||
.await
|
||||
.result
|
||||
.unwrap();
|
||||
|
||||
assert!(result["from failure module"]["error"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("message")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("Uncaught (in promise) [ 0, 1 ]"));
|
||||
.contains("[0,1]"));
|
||||
|
||||
let result = RunJob::from(JobPayload::RawFlow { value: flow.clone(), path: None })
|
||||
.arg("n", json!(3))
|
||||
|
||||
@@ -69,3 +69,5 @@ hmac.workspace = true
|
||||
cookie.workspace = true
|
||||
sha2.workspace = true
|
||||
urlencoding.workspace = true
|
||||
async-stripe.workspace = true
|
||||
lazy_static.workspace = true
|
||||
9
backend/windmill-api/build_openapi.sh
Executable file
9
backend/windmill-api/build_openapi.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
npx @redocly/openapi-cli@latest bundle openapi.yaml > openapi-bundled.yaml
|
||||
|
||||
|
||||
npx @redocly/openapi-cli@latest bundle openapi-bundled.yaml --ext yaml -d > openapi-deref.yaml
|
||||
|
||||
rm openapi-bundled.yaml
|
||||
0
backend/windmill-api/openapi-deref.yaml
Normal file
0
backend/windmill-api/openapi-deref.yaml
Normal file
@@ -1,10 +1,11 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.60.0
|
||||
title: Windmill server API
|
||||
version: 1.61.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
name: Windmill contact
|
||||
name: Windmill Team
|
||||
email: contact@windmill.dev
|
||||
url: https://windmill.dev
|
||||
|
||||
@@ -12,6 +13,8 @@ info:
|
||||
name: Apache 2.0
|
||||
url: https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
x-logo:
|
||||
url: https://docs.windmill.dev/img/windmill.svg
|
||||
externalDocs:
|
||||
description: documentation portal
|
||||
url: https://docs.windmill.dev
|
||||
@@ -876,6 +879,10 @@ paths:
|
||||
type: string
|
||||
auto_invite_operator:
|
||||
type: boolean
|
||||
plan:
|
||||
type: string
|
||||
customer_id:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/premium_info:
|
||||
get:
|
||||
@@ -2421,6 +2428,45 @@ paths:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: job result
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
|
||||
/w/{workspace}/jobs/run_wait_result/f/{path}:
|
||||
post:
|
||||
summary: run flow by path and wait until completion
|
||||
operationId: runWaitResultFlowByPath
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
- name: scheduled_for
|
||||
description: when to schedule this job (leave empty for immediate run)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- $ref: "#/components/parameters/QueueLimit"
|
||||
|
||||
requestBody:
|
||||
description: script args
|
||||
@@ -4458,6 +4504,13 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
QueueLimit:
|
||||
name: queue_limit
|
||||
description: |
|
||||
The maximum size of the queue for which the request would get rejected if that job would push it above that limit
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
|
||||
ScriptStartPath:
|
||||
name: script_path_start
|
||||
|
||||
@@ -31,7 +31,7 @@ use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
apps::ListAppQuery,
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
users::owner_to_token_owner,
|
||||
users::username_to_permissioned_as,
|
||||
utils::{
|
||||
http_get_from_hub, list_elems_from_hub, not_found_if_none, paginate, Pagination, StripPath,
|
||||
},
|
||||
@@ -610,7 +610,7 @@ async fn execute_component(
|
||||
})?;
|
||||
(
|
||||
username.clone(),
|
||||
owner_to_token_owner(&username, false),
|
||||
username_to_permissioned_as(&username),
|
||||
email,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ async fn create_flow(
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
windmill_common::users::owner_to_token_owner(&authed.username, false),
|
||||
windmill_common::users::username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -377,7 +377,7 @@ async fn update_flow(
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
windmill_common::users::owner_to_token_owner(&authed.username, false),
|
||||
windmill_common::users::username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -19,7 +19,7 @@ use itertools::Itertools;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
error::{self, Error, JsonResult, Result},
|
||||
users::owner_to_token_owner,
|
||||
users::username_to_permissioned_as,
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
};
|
||||
|
||||
@@ -145,7 +145,7 @@ async fn create_folder(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
check_name_conflict(&mut tx, &w_id, &ng.name).await?;
|
||||
let owner = owner_to_token_owner(&authed.username, false);
|
||||
let owner = username_to_permissioned_as(&authed.username);
|
||||
let owners = &ng.owners.unwrap_or(vec![owner.clone()]);
|
||||
|
||||
if let Some(extra_perms) = ng.extra_perms.clone() {
|
||||
|
||||
@@ -16,9 +16,9 @@ use axum::{
|
||||
Json, Router,
|
||||
};
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::{
|
||||
error::{Error, JsonResult, Result},
|
||||
users::owner_to_token_owner,
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
};
|
||||
|
||||
@@ -199,7 +199,7 @@ async fn create_group(
|
||||
w_id,
|
||||
ng.name,
|
||||
ng.summary,
|
||||
serde_json::json!({owner_to_token_owner(&authed.username, false): true})
|
||||
serde_json::json!({username_to_permissioned_as(&authed.username): true})
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
@@ -29,7 +29,7 @@ use windmill_common::{
|
||||
flows::FlowValue,
|
||||
oauth2::HmacSha256,
|
||||
scripts::{ScriptHash, ScriptLang},
|
||||
users::owner_to_token_owner,
|
||||
users::username_to_permissioned_as,
|
||||
utils::{not_found_if_none, now_from_db, paginate, require_admin, Pagination, StripPath},
|
||||
};
|
||||
use windmill_queue::{get_queued_job, push, JobKind, JobPayload, QueuedJob, RawCode};
|
||||
@@ -38,7 +38,7 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::{require_owner_of_path, Authed},
|
||||
variables::get_workspace_key,
|
||||
BaseUrl,
|
||||
BaseUrl, QueueLimitWaitResult, TimeoutWaitResult,
|
||||
};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -53,6 +53,10 @@ pub fn workspaced_service() -> Router {
|
||||
"/run_wait_result/h/:hash",
|
||||
post(run_wait_result_job_by_hash),
|
||||
)
|
||||
.route(
|
||||
"/run_wait_result/f/*script_path",
|
||||
post(run_wait_result_flow_by_path),
|
||||
)
|
||||
.route("/run/h/:hash", post(run_job_by_hash))
|
||||
.route("/run/preview", post(run_preview_job))
|
||||
.route("/run/preview_flow", post(run_preview_flow_job))
|
||||
@@ -263,6 +267,15 @@ pub struct RunJobQuery {
|
||||
parent_job: Option<Uuid>,
|
||||
include_header: Option<String>,
|
||||
invisible_to_owner: Option<bool>,
|
||||
queue_limit: Option<i64>,
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref INCLUDE_HEADERS: Vec<String> = std::env::var("INCLUDE_HEADERS")
|
||||
.ok().map(|x| x
|
||||
.split(',')
|
||||
.map(|s| s.to_string())
|
||||
.collect()).unwrap_or_default();
|
||||
}
|
||||
|
||||
impl RunJobQuery {
|
||||
@@ -285,11 +298,14 @@ impl RunJobQuery {
|
||||
headers: HeaderMap,
|
||||
mut args: serde_json::Map<String, serde_json::Value>,
|
||||
) -> serde_json::Map<String, serde_json::Value> {
|
||||
self.include_header
|
||||
let whitelist = self
|
||||
.include_header
|
||||
.as_ref()
|
||||
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
whitelist
|
||||
.iter()
|
||||
.chain(INCLUDE_HEADERS.iter())
|
||||
.for_each(|h| {
|
||||
if let Some(v) = headers.get(h) {
|
||||
args.insert(
|
||||
@@ -1139,7 +1155,7 @@ pub async fn run_flow_by_path(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
@@ -1174,7 +1190,7 @@ pub async fn run_job_by_path(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
@@ -1188,16 +1204,66 @@ pub async fn run_job_by_path(
|
||||
Ok((StatusCode::CREATED, uuid.to_string()))
|
||||
}
|
||||
|
||||
struct Guard {
|
||||
done: bool,
|
||||
id: Uuid,
|
||||
w_id: String,
|
||||
db: UserDB,
|
||||
authed: Authed,
|
||||
}
|
||||
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
if !&self.done {
|
||||
let id = self.id;
|
||||
let username = self.authed.username.clone();
|
||||
let w_id = self.w_id.clone();
|
||||
let db = self.db.clone();
|
||||
let authed = self.authed.clone();
|
||||
|
||||
tracing::info!("http connection broke, marking job {id} as canceled");
|
||||
tokio::spawn(async move {
|
||||
let tx = db.begin(&authed).await.ok();
|
||||
if let Some(mut tx) = tx {
|
||||
let _ = sqlx::query!(
|
||||
"UPDATE queue SET canceled = true, canceled_reason = 'http connection broke', canceled_by = $1 WHERE id = $2 AND workspace_id = $3",
|
||||
username,
|
||||
id,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await;
|
||||
let _ = tx.commit().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_wait_result<T>(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
timeout: i32,
|
||||
uuid: Uuid,
|
||||
Path((w_id, _)): Path<(String, T)>,
|
||||
) -> error::JsonResult<serde_json::Value> {
|
||||
let mut result = None;
|
||||
for i in 0..48 {
|
||||
let iters = if timeout <= 0 {
|
||||
20
|
||||
} else if timeout <= 1 {
|
||||
timeout * 10
|
||||
} else {
|
||||
10 + ((timeout - 1) * 2)
|
||||
};
|
||||
let mut g = Guard {
|
||||
done: false,
|
||||
id: uuid,
|
||||
w_id: w_id.clone(),
|
||||
db: user_db.clone(),
|
||||
authed: authed.clone(),
|
||||
};
|
||||
for i in 0..iters {
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
|
||||
result = sqlx::query_scalar!(
|
||||
"SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
uuid,
|
||||
@@ -1206,6 +1272,7 @@ async fn run_wait_result<T>(
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.flatten();
|
||||
drop(tx);
|
||||
|
||||
if result.is_some() {
|
||||
break;
|
||||
@@ -1214,20 +1281,45 @@ async fn run_wait_result<T>(
|
||||
tokio::time::sleep(core::time::Duration::from_millis(delay)).await;
|
||||
}
|
||||
if let Some(result) = result {
|
||||
g.done = true;
|
||||
Ok(Json(result))
|
||||
} else {
|
||||
Err(Error::ExecutionErr("timeout after 20s".to_string()))
|
||||
Err(Error::ExecutionErr(format!("timeout after {}s", timeout)))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_queue_too_long(db: DB, queue_limit: Option<i64>) -> error::Result<()> {
|
||||
if let Some(limit) = queue_limit {
|
||||
let count = sqlx::query_scalar!(
|
||||
"SELECT COUNT(*) FROM queue WHERE canceled = false AND (scheduled_for <= now()
|
||||
OR (suspend_until IS NOT NULL
|
||||
AND ( suspend <= 0
|
||||
OR suspend_until <= now())))",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
if count > queue_limit.unwrap() {
|
||||
return Err(Error::InternalErr(format!(
|
||||
"Number of queued job is too high: {count} > {limit}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn run_wait_result_job_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
|
||||
Extension(queue_limit): Extension<Arc<QueueLimitWaitResult>>,
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
headers: HeaderMap,
|
||||
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
|
||||
) -> error::JsonResult<serde_json::Value> {
|
||||
check_queue_too_long(db, queue_limit.0.or(run_query.queue_limit)).await?;
|
||||
let script_path = script_path.to_path();
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
|
||||
@@ -1242,7 +1334,7 @@ pub async fn run_wait_result_job_by_path(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
@@ -1254,17 +1346,28 @@ pub async fn run_wait_result_job_by_path(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(authed, Extension(user_db), uuid, Path((w_id, script_path))).await
|
||||
run_wait_result(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
timeout.0,
|
||||
uuid,
|
||||
Path((w_id, script_path)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_job_by_hash(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
|
||||
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
headers: HeaderMap,
|
||||
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
|
||||
) -> error::JsonResult<serde_json::Value> {
|
||||
check_queue_too_long(db, run_query.queue_limit).await?;
|
||||
|
||||
let hash = script_hash.0;
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let path = get_path_for_hash(&mut tx, &w_id, hash).await?;
|
||||
@@ -1278,7 +1381,7 @@ pub async fn run_wait_result_job_by_hash(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
@@ -1290,7 +1393,61 @@ pub async fn run_wait_result_job_by_hash(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(authed, Extension(user_db), uuid, Path((w_id, script_hash))).await
|
||||
run_wait_result(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
timeout.0,
|
||||
uuid,
|
||||
Path((w_id, script_hash)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_wait_result_flow_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
headers: HeaderMap,
|
||||
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
|
||||
) -> error::JsonResult<serde_json::Value> {
|
||||
check_queue_too_long(db, run_query.queue_limit).await?;
|
||||
|
||||
let flow_path = flow_path.to_path();
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let scheduled_for = run_query.get_scheduled_for(&mut tx).await?;
|
||||
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
|
||||
|
||||
let (uuid, tx) = push(
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::Flow(flow_path.to_string()),
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
!run_query.invisible_to_owner.unwrap_or(false),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
run_wait_result(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
timeout.0,
|
||||
uuid,
|
||||
Path((w_id, flow_path)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// a similar function exists on the worker
|
||||
@@ -1332,7 +1489,7 @@ async fn run_preview_job(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
@@ -1365,7 +1522,7 @@ async fn run_preview_flow_job(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
None,
|
||||
@@ -1400,7 +1557,7 @@ pub async fn run_job_by_hash(
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
scheduled_for,
|
||||
None,
|
||||
run_query.parent_job,
|
||||
|
||||
@@ -53,6 +53,8 @@ pub struct IsSecure(bool);
|
||||
pub struct CookieDomain(Option<String>);
|
||||
pub struct CloudHosted(bool);
|
||||
pub struct ContentSecurityPolicy(String);
|
||||
pub struct TimeoutWaitResult(i32);
|
||||
pub struct QueueLimitWaitResult(Option<i64>);
|
||||
|
||||
pub use users::delete_expired_items_perdiodically;
|
||||
|
||||
@@ -91,15 +93,15 @@ pub async fn run_server(
|
||||
.layer(Extension(auth_cache.clone()))
|
||||
.layer(Extension(basic_clients))
|
||||
.layer(Extension(Arc::new(BaseUrl(base_url.to_string()))))
|
||||
.layer(Extension(Arc::new(ContentSecurityPolicy(
|
||||
std::env::var("SERVE_CSP").unwrap_or("".to_owned()),
|
||||
))))
|
||||
.layer(Extension(Arc::new(CloudHosted(
|
||||
std::env::var("CLOUD_HOSTED").is_ok(),
|
||||
))))
|
||||
.layer(Extension(Arc::new(IsSecure(
|
||||
base_url.starts_with("https://"),
|
||||
))))
|
||||
.layer(Extension(Arc::new(ContentSecurityPolicy(
|
||||
std::env::var("SERVE_CSP").unwrap_or("".to_owned()),
|
||||
))))
|
||||
.layer(Extension(Arc::new(CookieDomain(
|
||||
std::env::var("COOKIE_DOMAIN").ok(),
|
||||
))))
|
||||
@@ -114,7 +116,21 @@ pub async fn run_server(
|
||||
"/w/:workspace_id",
|
||||
Router::new()
|
||||
.nest("/scripts", scripts::workspaced_service())
|
||||
.nest("/jobs", jobs::workspaced_service())
|
||||
.nest(
|
||||
"/jobs",
|
||||
jobs::workspaced_service()
|
||||
.layer(Extension(Arc::new(TimeoutWaitResult(
|
||||
std::env::var("TIMEOUT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(20),
|
||||
))))
|
||||
.layer(Extension(Arc::new(QueueLimitWaitResult(
|
||||
std::env::var("QUEUE_LIMIT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok()),
|
||||
)))),
|
||||
)
|
||||
.nest(
|
||||
"/users",
|
||||
users::workspaced_service().layer(Extension(argon2.clone())),
|
||||
@@ -186,7 +202,7 @@ async fn git_v() -> &'static str {
|
||||
}
|
||||
|
||||
async fn openapi() -> &'static str {
|
||||
include_str!("../openapi.yaml")
|
||||
include_str!("../openapi-deref.yaml")
|
||||
}
|
||||
pub async fn migrate_db(db: &DB) -> anyhow::Result<()> {
|
||||
db::migrate(db).await?;
|
||||
|
||||
@@ -36,7 +36,7 @@ use windmill_common::{
|
||||
to_i64, HubScript, ListScriptQuery, ListableScript, NewScript, Script, ScriptHash,
|
||||
ScriptKind, ScriptLang,
|
||||
},
|
||||
users::owner_to_token_owner,
|
||||
users::username_to_permissioned_as,
|
||||
utils::{
|
||||
list_elems_from_hub, not_found_if_none, paginate, require_admin, Pagination, StripPath,
|
||||
},
|
||||
@@ -374,7 +374,7 @@ async fn create_script(
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{Extension, FromRequestParts, Path, Query},
|
||||
extract::{Extension, FromRequestParts, OriginalUri, Path, Query},
|
||||
http::{self, request::Parts},
|
||||
response::{IntoResponse, Response},
|
||||
routing::{delete, get, post},
|
||||
@@ -177,7 +177,16 @@ impl AuthCache {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
let groups = vec![];
|
||||
let folders = vec![];
|
||||
Some(Authed {
|
||||
email: email
|
||||
.unwrap_or_else(|| "missing@email.xyz".to_string()),
|
||||
username: owner,
|
||||
is_admin: super_admin,
|
||||
groups,
|
||||
folders,
|
||||
})
|
||||
}
|
||||
}
|
||||
(_, Some(email), super_admin) => {
|
||||
@@ -365,9 +374,15 @@ where
|
||||
} else {
|
||||
extract_token(parts, state).await
|
||||
};
|
||||
let path_vec: Vec<&str> = parts.uri.path().split("/").collect();
|
||||
let workspace_id = if path_vec[0] == "" && path_vec[1] == "w" {
|
||||
Some(path_vec[2].to_owned())
|
||||
let original_uri = OriginalUri::from_request_parts(parts, state)
|
||||
.await
|
||||
.ok()
|
||||
.map(|x| x.0)
|
||||
.unwrap_or_default();
|
||||
let path_vec: Vec<&str> = original_uri.path().split("/").collect();
|
||||
|
||||
let workspace_id = if path_vec.len() >= 4 && path_vec[0] == "" && path_vec[2] == "w" {
|
||||
Some(path_vec[3].to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -6,24 +6,28 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
folders::Folder,
|
||||
resources::{Resource, ResourceType},
|
||||
users::{Authed, WorkspaceInvite},
|
||||
utils::require_super_admin,
|
||||
BaseUrl,
|
||||
};
|
||||
use axum::{
|
||||
body::StreamBody,
|
||||
extract::{Extension, Path, Query},
|
||||
headers,
|
||||
response::IntoResponse,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use stripe::CustomerId;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
error::{Error, JsonResult, Result},
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
flows::Flow,
|
||||
scripts::{Schema, Script, ScriptLang},
|
||||
utils::{paginate, rd_string, require_admin, Pagination},
|
||||
@@ -50,6 +54,8 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/edit_auto_invite", post(edit_auto_invite))
|
||||
.route("/tarball", get(tarball_workspace))
|
||||
.route("/premium_info", get(premium_info))
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal))
|
||||
}
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
@@ -82,6 +88,8 @@ pub struct WorkspaceSettings {
|
||||
pub slack_email: String,
|
||||
pub auto_invite_domain: Option<String>,
|
||||
pub auto_invite_operator: Option<bool>,
|
||||
pub customer_id: Option<String>,
|
||||
pub plan: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -201,6 +209,148 @@ async fn premium_info(
|
||||
Ok(Json(row))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PlanQuery {
|
||||
plan: String,
|
||||
}
|
||||
|
||||
async fn stripe_checkout(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
Query(plan): Query<PlanQuery>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
) -> Result<Redirect> {
|
||||
// #[cfg(feature = "enterprise")]
|
||||
{
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
|
||||
let success_rd = format!("{}/workspace_settings/checkout?success=true", base_url.0);
|
||||
let failure_rd = format!("{}/workspace_settings/checkout?success=false", base_url.0);
|
||||
let checkout_session = {
|
||||
let mut params = stripe::CreateCheckoutSession::new(&failure_rd, &success_rd);
|
||||
params.mode = Some(stripe::CheckoutSessionMode::Subscription);
|
||||
params.line_items = match plan.plan.as_str() {
|
||||
"team" => Some(vec![
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: Some(1),
|
||||
price: Some("price_1MUlrWGU3NdFi9eLE9GBZhoY".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: None,
|
||||
price: Some("price_1MUlreGU3NdFi9eLi6sOyvVa".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: None,
|
||||
price: Some("price_1MUlrlGU3NdFi9eLFLggSXZV".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: None,
|
||||
price: Some("price_1MUlr3GU3NdFi9eLbZYFjR9p".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
]),
|
||||
// "enterprise" => Some(vec![
|
||||
// stripe::CreateCheckoutSessionLineItems {
|
||||
// quantity: None,
|
||||
// price: Some("price_1MSdf6GU3NdFi9eLJFRkntlx".to_string()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// stripe::CreateCheckoutSessionLineItems {
|
||||
// quantity: None,
|
||||
// price: Some("price_1MShsNGU3NdFi9eLJMEZUW8b".to_string()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ]),
|
||||
_ => Err(Error::BadRequest("invalid plan".to_string()))?,
|
||||
};
|
||||
params.customer_email = Some(&authed.email);
|
||||
params.client_reference_id = Some(&w_id);
|
||||
stripe::CheckoutSession::create(&client, params)
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
let uri = checkout_session
|
||||
.url
|
||||
.ok_or_else(|| Error::InternalErr(format!("stripe checkout redirect issue")))?;
|
||||
Ok(Redirect::to(&uri))
|
||||
}
|
||||
}
|
||||
|
||||
async fn stripe_portal(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
) -> Result<Redirect> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let customer_id = sqlx::query_scalar!(
|
||||
"SELECT customer_id FROM workspace_settings WHERE workspace_id = $1",
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("no customer id for workspace {}", w_id)))?;
|
||||
let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
|
||||
let success_rd = format!("{}/workspace_settings?tab=premium", base_url.0);
|
||||
let portal_session = {
|
||||
let customer_id = CustomerId::from_str(&customer_id).unwrap();
|
||||
let mut params = stripe::CreateBillingPortalSession::new(customer_id);
|
||||
params.return_url = Some(&success_rd);
|
||||
stripe::BillingPortalSession::create(&client, params)
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
};
|
||||
Ok(Redirect::to(&portal_session.url))
|
||||
}
|
||||
|
||||
// async fn stripe_usage(
|
||||
// authed: Authed,
|
||||
// Path(w_id): Path<String>,
|
||||
// Extension(db): Extension<DB>,
|
||||
// Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
// ) -> Result<Redirect> {
|
||||
// require_admin(authed.is_admin, &authed.username)?;
|
||||
// let customer_id = sqlx::query_scalar!(
|
||||
// "SELECT customer_id FROM workspace_settings WHERE workspace_id = $1",
|
||||
// w_id
|
||||
// )
|
||||
// .fetch_one(&db)
|
||||
// .await?
|
||||
// .ok_or_else(|| Error::InternalErr(format!("no customer id for workspace {}", w_id)))?;
|
||||
// let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
|
||||
// let success_rd = format!("{}/workspace_settings?tab=premium", base_url.0);
|
||||
// let portal_session = {
|
||||
// let customer_id = CustomerId::from_str(&customer_id).unwrap();
|
||||
// let subscriptions = stripe::Subscription::list(
|
||||
// &client,
|
||||
// stripe::ListSubscriptions { customer: Some(customer_id), ..Default::default() },
|
||||
// )
|
||||
// .await
|
||||
// .map_err(to_anyhow)?
|
||||
// .data[0];
|
||||
// let getUsage =
|
||||
// stripe::SubscriptionItem::list(
|
||||
// &client,
|
||||
// stripe::ListSubscriptionItems {
|
||||
// subscription: subscription.id,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// )
|
||||
// .await
|
||||
// .map_err(to_anyhow)
|
||||
// };
|
||||
// let mut params = stripe::ListSubscriptionItems::new(customer_id);
|
||||
// params.return_url = Some(&success_rd);
|
||||
// stripe::BillingPortalSession::create(&client, params)
|
||||
// .await
|
||||
// .map_err(to_anyhow)?
|
||||
// };
|
||||
// }
|
||||
|
||||
async fn exists_workspace(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
|
||||
@@ -58,6 +58,8 @@ pub enum Error {
|
||||
ExitStatus(i32),
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
#[error("Error: {0:#?}")]
|
||||
JsonErr(serde_json::Value),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
||||
@@ -11,7 +11,9 @@ use std::{collections::HashMap, time::Duration};
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
more_serde::{default_false, default_id, default_true, is_default},
|
||||
more_serde::{
|
||||
default_empty_string, default_false, default_id, default_null, default_true, is_default,
|
||||
},
|
||||
scripts::{Schema, ScriptHash, ScriptLang},
|
||||
};
|
||||
|
||||
@@ -172,8 +174,14 @@ impl FlowModule {
|
||||
rename_all(serialize = "lowercase", deserialize = "lowercase")
|
||||
)]
|
||||
pub enum InputTransform {
|
||||
Static { value: serde_json::Value },
|
||||
Javascript { expr: String },
|
||||
Static {
|
||||
#[serde(default = "default_null")]
|
||||
value: serde_json::Value,
|
||||
},
|
||||
Javascript {
|
||||
#[serde(default = "default_empty_string")]
|
||||
expr: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
||||
@@ -18,6 +18,14 @@ pub fn default_false() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn default_null() -> serde_json::Value {
|
||||
serde_json::Value::Null
|
||||
}
|
||||
|
||||
pub fn default_empty_string() -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn default_id() -> String {
|
||||
rd_string(6)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,11 @@ pub fn initialize_tracing() {
|
||||
|
||||
match json_fmt {
|
||||
true => ts_base
|
||||
.with(json_layer().with_filter(filter_fn(filter_metadata)))
|
||||
.with(
|
||||
json_layer()
|
||||
.flatten_event(true)
|
||||
.with_filter(filter_fn(filter_metadata)),
|
||||
)
|
||||
.init(),
|
||||
false => ts_base
|
||||
.with(
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
pub fn owner_to_token_owner(user: &str, is_group: bool) -> String {
|
||||
let prefix = if is_group { 'g' } else { 'u' };
|
||||
format!("{}/{}", prefix, user)
|
||||
pub fn username_to_permissioned_as(user: &str) -> String {
|
||||
if user.contains('@') {
|
||||
user.to_string()
|
||||
} else {
|
||||
format!("u/{}", user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +72,6 @@ pub fn not_found_if_none<T, U: AsRef<str>>(opt: Option<T>, kind: &str, name: U)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_owner_from_path(path: &str) -> String {
|
||||
path.split('/').take(2).collect::<Vec<_>>().join("/")
|
||||
}
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub async fn list_elems_from_hub(
|
||||
http_client: reqwest::Client,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use tracing::{instrument, Instrument};
|
||||
@@ -44,6 +45,7 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
const MAX_FREE_EXECS: i32 = 1000;
|
||||
const MAX_FREE_CONCURRENT_RUNS: i32 = 3;
|
||||
|
||||
pub async fn cancel_job<'c>(
|
||||
username: &str,
|
||||
@@ -172,7 +174,6 @@ pub async fn get_result_by_id(
|
||||
"Flow result by id",
|
||||
format!("{}, {}", flow_id, node_id),
|
||||
)?;
|
||||
println!("result_id: {:#?}, {node_id}", result_id);
|
||||
|
||||
let value = match result_id {
|
||||
JobResult::ListJob(x) => {
|
||||
@@ -318,10 +319,38 @@ pub async fn push<'c>(
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
if !is_super_admin && usage > MAX_FREE_EXECS {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
|
||||
if !is_super_admin {
|
||||
if usage > MAX_FREE_EXECS {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
let in_queue =
|
||||
sqlx::query_scalar!("SELECT COUNT(id) FROM queue WHERE email = $1", email)
|
||||
.fetch_one(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
if in_queue > MAX_FREE_EXECS.into() {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the jobs in queue limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
|
||||
let concurrent_runs = sqlx::query_scalar!(
|
||||
"SELECT COUNT(id) FROM queue WHERE running = true AND email = $1",
|
||||
email
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
if concurrent_runs > MAX_FREE_CONCURRENT_RUNS.into() {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the concurrent runs limit of {MAX_FREE_CONCURRENT_RUNS} that applies outside of premium workspaces."
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +381,9 @@ pub async fn push<'c>(
|
||||
)
|
||||
}
|
||||
JobPayload::ScriptHub { path } => {
|
||||
let script = get_hub_script(path.clone(), email).await?;
|
||||
let script = get_hub_script(path.clone(), email)
|
||||
.await
|
||||
.context("error fetching hub script")?;
|
||||
(
|
||||
None,
|
||||
Some(path),
|
||||
@@ -545,13 +576,13 @@ pub async fn push<'c>(
|
||||
Ok((uuid, tx))
|
||||
}
|
||||
|
||||
pub fn canceled_job_to_result(job: &QueuedJob) -> String {
|
||||
pub fn canceled_job_to_result(job: &QueuedJob) -> serde_json::Value {
|
||||
let reason = job
|
||||
.canceled_reason
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| "no reason given");
|
||||
let canceler = job.canceled_by.as_deref().unwrap_or_else(|| "unknown");
|
||||
format!("Job canceled: {reason} by {canceler}")
|
||||
serde_json::json!({"message": format!("Job canceled: {reason} by {canceler}"), "name": "Canceled", "reason": reason, "canceler": canceler})
|
||||
}
|
||||
|
||||
pub async fn get_hub_script(path: String, email: &str) -> error::Result<HubScript> {
|
||||
|
||||
@@ -13,7 +13,8 @@ use sqlx::{query_scalar, Postgres, Transaction};
|
||||
use windmill_common::{
|
||||
error::{self, Result},
|
||||
schedule::Schedule,
|
||||
utils::{get_owner_from_path, now_from_db, StripPath},
|
||||
users::username_to_permissioned_as,
|
||||
utils::{now_from_db, StripPath},
|
||||
};
|
||||
|
||||
use crate::{push, JobPayload};
|
||||
@@ -80,7 +81,7 @@ pub async fn push_scheduled_job<'c>(
|
||||
args,
|
||||
&schedule_to_user(&schedule.path),
|
||||
&schedule.email,
|
||||
get_owner_from_path(&schedule.path),
|
||||
username_to_permissioned_as(&schedule.edited_by),
|
||||
Some(next),
|
||||
Some(schedule.path.clone()),
|
||||
None,
|
||||
|
||||
@@ -3,12 +3,11 @@ name: "bash run script"
|
||||
mode: ONCE
|
||||
hostname: "bash"
|
||||
log_level: ERROR
|
||||
time_limit: 300
|
||||
|
||||
rlimit_as: 2048
|
||||
rlimit_as: 4096
|
||||
rlimit_cpu: 1000
|
||||
rlimit_fsize: 1024
|
||||
rlimit_nofile: 64
|
||||
rlimit_fsize: 1000
|
||||
rlimit_nofile: 10000
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
@@ -55,7 +54,7 @@ mount {
|
||||
dst: "/tmp"
|
||||
fstype: "tmpfs"
|
||||
rw: true
|
||||
options: "size=500000000"
|
||||
options: "size=800000000"
|
||||
}
|
||||
|
||||
mount {
|
||||
@@ -87,4 +86,4 @@ iface_no_lo: true
|
||||
|
||||
{SHARED_MOUNT}
|
||||
|
||||
|
||||
envar: "HOME=/tmp"
|
||||
|
||||
@@ -3,12 +3,11 @@ name: "deno run script"
|
||||
mode: ONCE
|
||||
hostname: "deno"
|
||||
log_level: ERROR
|
||||
time_limit: 300
|
||||
|
||||
rlimit_as: 16000
|
||||
rlimit_cpu: 1000
|
||||
rlimit_fsize: 1024
|
||||
rlimit_nofile: 64
|
||||
rlimit_fsize: 1000
|
||||
rlimit_nofile: 10000
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
@@ -73,6 +72,12 @@ mount {
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/import_map.json"
|
||||
dst: "/tmp/import_map.json"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/lock.json"
|
||||
dst: "/tmp/lock.json"
|
||||
|
||||
@@ -3,12 +3,11 @@ name: "go run script"
|
||||
mode: ONCE
|
||||
hostname: "go"
|
||||
log_level: ERROR
|
||||
time_limit: 300
|
||||
|
||||
rlimit_as: 2048
|
||||
rlimit_as: 4096
|
||||
rlimit_cpu: 1000
|
||||
rlimit_fsize: 1024
|
||||
rlimit_nofile: 64
|
||||
rlimit_fsize: 1000
|
||||
rlimit_nofile: 10000
|
||||
|
||||
cwd: "/tmp/go"
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ name: "python run script"
|
||||
mode: ONCE
|
||||
hostname: "python"
|
||||
log_level: ERROR
|
||||
time_limit: 300
|
||||
|
||||
rlimit_as: 2048
|
||||
rlimit_as: 4096
|
||||
rlimit_cpu: 1000
|
||||
rlimit_fsize: 1024
|
||||
rlimit_nofile: 64
|
||||
rlimit_fsize: 1000
|
||||
rlimit_nofile: 10000
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use tracing::instrument;
|
||||
use uuid::Uuid;
|
||||
@@ -14,36 +13,17 @@ use windmill_common::{error::Error, flow_status::FlowStatusModule, schedule::Sch
|
||||
use windmill_queue::{delete_job, schedule::get_schedule_opt, JobKind, QueuedJob};
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn add_completed_job_error<E: ToString + std::fmt::Debug>(
|
||||
pub async fn add_completed_job_error(
|
||||
db: &Pool<Postgres>,
|
||||
queued_job: &QueuedJob,
|
||||
logs: String,
|
||||
e: E,
|
||||
e: serde_json::Value,
|
||||
metrics: Option<crate::worker::Metrics>,
|
||||
) -> Result<(Uuid, serde_json::Map<String, serde_json::Value>), Error> {
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
metrics.map(|m| m.worker_execution_failed.inc());
|
||||
let mut output_map = Map::new();
|
||||
error_to_result(&mut output_map, &e);
|
||||
let a = add_completed_job(
|
||||
db,
|
||||
&queued_job,
|
||||
false,
|
||||
false,
|
||||
serde_json::Value::Object(output_map.clone()),
|
||||
logs,
|
||||
)
|
||||
.await?;
|
||||
Ok((a, output_map))
|
||||
}
|
||||
|
||||
pub fn error_to_result<E: ToString + std::fmt::Debug>(
|
||||
output_map: &mut Map<String, Value>,
|
||||
err: &E,
|
||||
) {
|
||||
output_map.insert(
|
||||
"error".to_string(),
|
||||
serde_json::Value::String(err.to_string()),
|
||||
);
|
||||
let result = serde_json::json!({ "error": e });
|
||||
let _ = add_completed_job(db, &queued_job, false, false, result.clone(), logs).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn flatten_jobs(modules: Vec<FlowStatusModule>) -> Vec<Uuid> {
|
||||
@@ -130,7 +110,7 @@ pub async fn add_completed_job(
|
||||
, email
|
||||
, visible_to_owner
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, EXTRACT(milliseconds FROM (now() - $6))), $7, $8, $9,\
|
||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,\
|
||||
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)
|
||||
ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)",
|
||||
queued_job.workspace_id,
|
||||
|
||||
@@ -12,7 +12,10 @@ use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use std::{borrow::Borrow, collections::HashMap, io, panic, process::Stdio, time::Duration};
|
||||
use std::{
|
||||
borrow::Borrow, collections::HashMap, io, os::unix::process::ExitStatusExt, panic,
|
||||
process::Stdio, time::Duration,
|
||||
};
|
||||
use tracing::{trace_span, Instrument};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -25,7 +28,7 @@ use windmill_common::{
|
||||
};
|
||||
use windmill_queue::{canceled_job_to_result, get_queued_job, pull, JobKind, QueuedJob};
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use tokio::{
|
||||
fs::{metadata, symlink, DirBuilder, File},
|
||||
@@ -46,7 +49,7 @@ use futures::{
|
||||
use async_recursion::async_recursion;
|
||||
|
||||
use crate::{
|
||||
jobs::{add_completed_job, add_completed_job_error, error_to_result},
|
||||
jobs::{add_completed_job, add_completed_job_error},
|
||||
worker_flow::{
|
||||
handle_flow, update_flow_status_after_job_completion, update_flow_status_in_progress,
|
||||
},
|
||||
@@ -130,7 +133,7 @@ async fn copy_cache_to_bucket_as_tar(bucket: &str) {
|
||||
.current_dir(ROOT_CACHE_DIR)
|
||||
.arg("-c")
|
||||
.arg("-f")
|
||||
.arg(format!("{ROOT_CACHE_DIR}/{TAR_CACHE_FILENAME}"))
|
||||
.arg(format!("{ROOT_CACHE_DIR}{TAR_CACHE_FILENAME}"))
|
||||
.args(&["pip", "go", "deno"])
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
@@ -178,7 +181,7 @@ async fn copy_cache_from_bucket_as_tar(bucket: &str) -> bool {
|
||||
match Command::new("rclone")
|
||||
.arg("copyto")
|
||||
.arg(format!(":s3,env_auth=true:{bucket}/{TAR_CACHE_FILENAME}"))
|
||||
.arg(format!("{ROOT_CACHE_DIR}/{TAR_CACHE_FILENAME}"))
|
||||
.arg(format!("{ROOT_CACHE_DIR}{TAR_CACHE_FILENAME}"))
|
||||
.arg("--size-only")
|
||||
.arg("--fast-list")
|
||||
.stdin(Stdio::null())
|
||||
@@ -231,29 +234,27 @@ pub async fn create_token_for_owner<'c>(
|
||||
owner: &str,
|
||||
label: &str,
|
||||
expires_in: i32,
|
||||
username: &str,
|
||||
email: &str,
|
||||
) -> error::Result<(Transaction<'c, Postgres>, String)> {
|
||||
// TODO: Bad implementation. We should not have access to this DB here.
|
||||
let token: String = rd_string(30);
|
||||
let is_super_admin = username.contains('@')
|
||||
&& sqlx::query_scalar!(
|
||||
"SELECT super_admin FROM password WHERE email = $1",
|
||||
owner.split_once('/').map(|x| x.1).unwrap_or("")
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
let is_super_admin = owner.contains('@')
|
||||
&& sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", owner)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
sqlx::query_scalar!(
|
||||
"INSERT INTO token
|
||||
(workspace_id, token, owner, label, expiration, super_admin)
|
||||
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)",
|
||||
(workspace_id, token, owner, label, expiration, super_admin, email)
|
||||
VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6, $7)",
|
||||
&w_id,
|
||||
token,
|
||||
owner,
|
||||
label,
|
||||
expires_in.to_string(),
|
||||
is_super_admin
|
||||
is_super_admin,
|
||||
email
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
@@ -277,7 +278,6 @@ const NSJAIL_CONFIG_RUN_DENO_CONTENT: &str = include_str!("../nsjail/run.deno.co
|
||||
|
||||
const RELATIVE_PYTHON_LOADER: &str = include_str!("../loader.py");
|
||||
|
||||
const MAX_LOG_SIZE: u32 = 200000;
|
||||
const GO_REQ_SPLITTER: &str = "//go.sum";
|
||||
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
|
||||
@@ -416,6 +416,26 @@ pub async fn run_worker(
|
||||
let pip_index_url = std::env::var("PIP_INDEX_URL").ok();
|
||||
let pip_extra_index_url = std::env::var("PIP_EXTRA_INDEX_URL").ok();
|
||||
let pip_trusted_host = std::env::var("PIP_TRUSTED_HOST").ok();
|
||||
let deno_auth_tokens = std::env::var("DENO_AUTH_TOKENS")
|
||||
.ok()
|
||||
.map(|x| format!(";{x}"))
|
||||
.unwrap_or_else(|| String::new());
|
||||
let max_log_size = std::env::var("MAX_LOG_SIZE")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i64>().ok())
|
||||
.unwrap_or(500000);
|
||||
let deno_flags = std::env::var("DENO_FLAGS")
|
||||
.ok()
|
||||
.map(|x| x.split(' ').map(|x| x.to_string()).collect());
|
||||
let pip_local_dependencies = std::env::var("PIP_LOCAL_DEPENDENCIES")
|
||||
.ok()
|
||||
.map(|x| x.split(',').map(|x| x.to_string()).collect());
|
||||
|
||||
let pip_local_dependencies = if pip_local_dependencies == Some(vec!["".to_string()]) {
|
||||
None
|
||||
} else {
|
||||
pip_local_dependencies
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let tar_cache_rate = std::env::var("TAR_CACHE_RATE")
|
||||
@@ -423,6 +443,10 @@ pub async fn run_worker(
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
.unwrap_or(100);
|
||||
|
||||
let additional_python_paths = std::env::var("ADDITIONAL_PYTHON_PATHS")
|
||||
.ok()
|
||||
.map(|x| x.split(':').map(|x| x.to_string()).collect());
|
||||
|
||||
let envs = Envs {
|
||||
deno_path,
|
||||
go_path,
|
||||
@@ -433,6 +457,11 @@ pub async fn run_worker(
|
||||
pip_index_url,
|
||||
pip_extra_index_url,
|
||||
pip_trusted_host,
|
||||
max_log_size,
|
||||
deno_flags,
|
||||
deno_auth_tokens,
|
||||
pip_local_dependencies,
|
||||
additional_python_paths,
|
||||
};
|
||||
WORKER_STARTED.inc();
|
||||
|
||||
@@ -567,11 +596,11 @@ pub async fn run_worker(
|
||||
&job.permissioned_as,
|
||||
"ephemeral-script",
|
||||
timeout * 2,
|
||||
&job.created_by,
|
||||
&job.email,
|
||||
)
|
||||
.await.expect("could not create job token");
|
||||
tx.commit().await.expect("could not commit job token");
|
||||
let job_client = windmill_api_client::create_client(&worker_config.base_url, token.clone());
|
||||
let job_client = windmill_api_client::create_client(&worker_config.base_internal_url, token.clone());
|
||||
let is_flow = job.job_kind == JobKind::Flow || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::FlowDependencies;
|
||||
|
||||
if let Some(err) = handle_queued_job(
|
||||
@@ -647,26 +676,29 @@ async fn handle_job_error(
|
||||
keep_job_dir: bool,
|
||||
base_internal_url: &str,
|
||||
) {
|
||||
add_completed_job_error(
|
||||
db,
|
||||
&job,
|
||||
format!("Unexpected error during job execution:\n{err}"),
|
||||
&err,
|
||||
metrics.clone(),
|
||||
)
|
||||
.await
|
||||
.map(|(_, m)| m)
|
||||
.unwrap_or_else(|_| Map::new());
|
||||
let err = match err {
|
||||
Error::JsonErr(err) => err,
|
||||
_ => json!({"message": err.to_string(), "name": "InternalErr"}),
|
||||
};
|
||||
|
||||
let update_job_future = || {
|
||||
add_completed_job_error(
|
||||
db,
|
||||
&job,
|
||||
format!("Unexpected error during job execution:\n{err}"),
|
||||
err.clone(),
|
||||
metrics.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
if job.is_flow_step || job.job_kind == JobKind::FlowPreview || job.job_kind == JobKind::Flow {
|
||||
let (flow, job_status_to_update) = if let Some(parent_job_id) = job.parent_job {
|
||||
let _ = update_job_future().await;
|
||||
(parent_job_id, job.id)
|
||||
} else {
|
||||
(job.id, Uuid::nil())
|
||||
};
|
||||
|
||||
let mut output_map = serde_json::Map::new();
|
||||
error_to_result(&mut output_map, &err);
|
||||
let updated_flow = update_flow_status_after_job_completion(
|
||||
db,
|
||||
client,
|
||||
@@ -674,7 +706,7 @@ async fn handle_job_error(
|
||||
&job_status_to_update,
|
||||
&job.workspace_id,
|
||||
false,
|
||||
serde_json::Value::Object(output_map),
|
||||
json!({ "error": err }),
|
||||
metrics.clone(),
|
||||
unrecoverable,
|
||||
same_worker_tx,
|
||||
@@ -684,9 +716,8 @@ async fn handle_job_error(
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = updated_flow {
|
||||
println!("error updating flow status: {}", err);
|
||||
|
||||
if let Err(err) = updated_flow {
|
||||
if let Some(parent_job_id) = job.parent_job {
|
||||
if let Ok(mut tx) = db.begin().await {
|
||||
if let Ok(Some(parent_job)) =
|
||||
@@ -696,7 +727,7 @@ async fn handle_job_error(
|
||||
db,
|
||||
&parent_job,
|
||||
format!("Unexpected error during flow job error handling:\n{err}"),
|
||||
err,
|
||||
json!({"message": err.to_string(), "name": "InternalErr"}),
|
||||
metrics.clone(),
|
||||
)
|
||||
.await;
|
||||
@@ -705,7 +736,10 @@ async fn handle_job_error(
|
||||
}
|
||||
}
|
||||
}
|
||||
tracing::error!(job_id = %job.id, err = err.alt(), "error handling job: {} {} {}", job.id, job.workspace_id, job.created_by);
|
||||
if job.parent_job.is_none() {
|
||||
let _ = update_job_future().await;
|
||||
}
|
||||
tracing::error!(job_id = %job.id, "error handling job: {err:#?} {} {} {}", job.id, job.workspace_id, job.created_by);
|
||||
}
|
||||
|
||||
async fn insert_initial_ping(
|
||||
@@ -735,8 +769,16 @@ struct Envs {
|
||||
pip_index_url: Option<String>,
|
||||
pip_extra_index_url: Option<String>,
|
||||
pip_trusted_host: Option<String>,
|
||||
deno_auth_tokens: String,
|
||||
deno_flags: Option<Vec<String>>,
|
||||
max_log_size: i64,
|
||||
pip_local_dependencies: Option<Vec<String>>,
|
||||
additional_python_paths: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
fn extract_error_value(log_lines: &str) -> serde_json::Value {
|
||||
return json!({"message": log_lines.to_string().trim().to_string(), "name": "ExecutionErr"});
|
||||
}
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_queued_job(
|
||||
job: QueuedJob,
|
||||
@@ -754,7 +796,7 @@ async fn handle_queued_job(
|
||||
base_internal_url: &str,
|
||||
) -> windmill_common::error::Result<()> {
|
||||
if job.canceled {
|
||||
return Err(Error::ExecutionErr(canceled_job_to_result(&job)))?;
|
||||
return Err(Error::JsonErr(canceled_job_to_result(&job)))?;
|
||||
}
|
||||
if let Some(e) = job.pre_run_error {
|
||||
return Err(Error::ExecutionErr(e));
|
||||
@@ -857,32 +899,36 @@ async fn handle_queued_job(
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_message = match e {
|
||||
let error_value = match e {
|
||||
Error::ExitStatus(_) => {
|
||||
let last_10_log_lines = logs
|
||||
.lines()
|
||||
.skip(logs.lines().count().max(13) - 13)
|
||||
.join("\n")
|
||||
.to_string()
|
||||
.replace("\n\n", "\n");
|
||||
let res = read_result(job_dir).await.ok();
|
||||
|
||||
let log_lines = last_10_log_lines
|
||||
.split("CODE EXECUTION ---")
|
||||
.last()
|
||||
.unwrap_or(&logs);
|
||||
format!("Error during execution of the script:\n{}", log_lines)
|
||||
if res.is_some() && res.clone().unwrap().is_object() {
|
||||
res.unwrap()
|
||||
} else {
|
||||
let last_10_log_lines = logs
|
||||
.lines()
|
||||
.skip(logs.lines().count().max(13) - 13)
|
||||
.join("\n")
|
||||
.to_string()
|
||||
.replace("\n\n", "\n");
|
||||
|
||||
let log_lines = last_10_log_lines
|
||||
.split("CODE EXECUTION ---")
|
||||
.last()
|
||||
.unwrap_or(&logs);
|
||||
|
||||
extract_error_value(log_lines)
|
||||
}
|
||||
}
|
||||
err @ _ => {
|
||||
json!({"message": format!("error during execution of the script:\n{}", err), "name": "ExecutionErr"})
|
||||
}
|
||||
err @ _ => format!("error before termination: {err:#?}"),
|
||||
};
|
||||
|
||||
let (_, output_map) = add_completed_job_error(
|
||||
db,
|
||||
&job,
|
||||
logs,
|
||||
error_message,
|
||||
Some(metrics.clone()),
|
||||
)
|
||||
.await?;
|
||||
let result =
|
||||
add_completed_job_error(db, &job, logs, error_value, Some(metrics.clone()))
|
||||
.await?;
|
||||
if job.is_flow_step {
|
||||
if let Some(parent_job) = job.parent_job {
|
||||
update_flow_status_after_job_completion(
|
||||
@@ -892,7 +938,7 @@ async fn handle_queued_job(
|
||||
&job.id,
|
||||
&job.workspace_id,
|
||||
false,
|
||||
serde_json::Value::Object(output_map),
|
||||
result,
|
||||
Some(metrics),
|
||||
false,
|
||||
same_worker_tx,
|
||||
@@ -922,6 +968,7 @@ async fn write_file(dir: &str, path: &str, content: &str) -> error::Result<File>
|
||||
|
||||
#[async_recursion]
|
||||
async fn transform_json_value(
|
||||
name: &str,
|
||||
client: &windmill_api_client::Client,
|
||||
workspace: &str,
|
||||
v: Value,
|
||||
@@ -932,32 +979,32 @@ async fn transform_json_value(
|
||||
let v = client
|
||||
.get_variable(workspace, path, Some(true))
|
||||
.await
|
||||
.map_err(to_anyhow)
|
||||
.map_err(|_| Error::NotFound(format!("Variable {path} not found for `{name}`")))
|
||||
.map(|v| v.into_inner())?
|
||||
.value
|
||||
.map_or_else(
|
||||
|| Err(Error::NotFound(format!("Variable {path} not found"))),
|
||||
|e| Ok(e),
|
||||
)?;
|
||||
.unwrap_or_else(|| String::new());
|
||||
Ok(Value::String(v))
|
||||
}
|
||||
Value::String(y) if y.starts_with("$res:") => {
|
||||
let path = y.strip_prefix("$res:").unwrap();
|
||||
if path.split("/").count() < 2 {
|
||||
return Err(Error::InternalErr(
|
||||
format!("invalid resource path: {path}",),
|
||||
));
|
||||
return Err(Error::InternalErr(format!(
|
||||
"Argument `{name}` is an invalid resource path: {path}",
|
||||
)));
|
||||
}
|
||||
let v = client
|
||||
.get_resource_value(workspace, path)
|
||||
.await
|
||||
.map_err(|_| Error::NotFound(format!("Resource {path} not found")))?
|
||||
.map_err(|_| Error::NotFound(format!("Resource {path} not found for `{name}`")))?
|
||||
.into_inner();
|
||||
transform_json_value(client, workspace, v).await
|
||||
transform_json_value(name, client, workspace, v).await
|
||||
}
|
||||
Value::Object(mut m) => {
|
||||
for (a, b) in m.clone().into_iter() {
|
||||
m.insert(a, transform_json_value(client, workspace, b).await?);
|
||||
m.insert(
|
||||
a.clone(),
|
||||
transform_json_value(&a, client, workspace, b).await?,
|
||||
);
|
||||
}
|
||||
Ok(Value::Object(m))
|
||||
}
|
||||
@@ -1120,7 +1167,7 @@ mount {{
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_go_job(
|
||||
WorkerConfig { base_internal_url, disable_nuser, disable_nsjail, base_url, .. }: &WorkerConfig,
|
||||
Envs { nsjail_path, go_path, path_env, home_env, .. }: &Envs,
|
||||
Envs { nsjail_path, go_path, path_env, home_env, max_log_size, .. }: &Envs,
|
||||
logs: &mut String,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -1158,6 +1205,7 @@ async fn handle_go_job(
|
||||
job_dir,
|
||||
db,
|
||||
timeout,
|
||||
*max_log_size,
|
||||
go_path,
|
||||
true,
|
||||
skip_go_mod,
|
||||
@@ -1278,7 +1326,7 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
handle_child(&job.id, db, logs, timeout, build_go).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, build_go).await?;
|
||||
|
||||
Command::new(nsjail_path)
|
||||
.current_dir(job_dir)
|
||||
@@ -1304,14 +1352,14 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?
|
||||
};
|
||||
handle_child(&job.id, db, logs, timeout, child).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_bash_job(
|
||||
WorkerConfig { base_internal_url, disable_nuser, disable_nsjail, base_url, .. }: &WorkerConfig,
|
||||
Envs { nsjail_path, path_env, home_env, .. }: &Envs,
|
||||
Envs { nsjail_path, path_env, home_env, max_log_size, .. }: &Envs,
|
||||
logs: &mut String,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -1383,7 +1431,7 @@ async fn handle_bash_job(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?
|
||||
};
|
||||
handle_child(&job.id, db, logs, timeout, child).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
//for now bash jobs have an empty result object
|
||||
Ok(serde_json::json!(logs
|
||||
.lines()
|
||||
@@ -1403,7 +1451,7 @@ fn capitalize(s: &str) -> String {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_deno_job(
|
||||
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
|
||||
Envs { nsjail_path, deno_path, path_env, .. }: &Envs,
|
||||
Envs { nsjail_path, deno_path, path_env, max_log_size, deno_auth_tokens, deno_flags, .. }: &Envs,
|
||||
logs: &mut String,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -1446,16 +1494,30 @@ async function run() {{
|
||||
await Deno.writeTextFile("result.json", res_json);
|
||||
Deno.exit(0);
|
||||
}}
|
||||
run();
|
||||
run().catch(async (e) => {{
|
||||
await Deno.writeTextFile("result.json", JSON.stringify({{ message: e.message, name: e.name, stack: e.stack }}));
|
||||
Deno.exit(1);
|
||||
}});
|
||||
"#,
|
||||
);
|
||||
write_file(job_dir, "main.ts", &wrapper_content).await?;
|
||||
let w_id = job.workspace_id.clone();
|
||||
let import_map = format!(
|
||||
r#"{{
|
||||
"imports": {{
|
||||
"/": "{base_internal_url}/api/w/{w_id}/scripts/raw/p/",
|
||||
"./": "./"
|
||||
}}
|
||||
}}"#
|
||||
);
|
||||
write_file(job_dir, "import_map.json", &import_map).await?;
|
||||
let mut reserved_variables = get_reserved_variables(job, &token, &base_url, db).await?;
|
||||
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
|
||||
|
||||
let hostname_base = base_url.split("://").last().unwrap_or("localhost");
|
||||
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
|
||||
let deno_auth_tokens = format!("{token}@{hostname_base};{token}@{hostname_internal}");
|
||||
let deno_auth_tokens =
|
||||
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens}",);
|
||||
let child = async {
|
||||
Ok(if !disable_nsjail {
|
||||
let _ = write_file(
|
||||
@@ -1477,9 +1539,16 @@ run();
|
||||
if lockfile.is_some() {
|
||||
args.push("--lock=/tmp/lock.json");
|
||||
}
|
||||
args.push("--import-map");
|
||||
args.push("/tmp/import_map.json");
|
||||
args.push("--unstable");
|
||||
args.push("--v8-flags=--max-heap-size=2048");
|
||||
args.push("-A");
|
||||
if let Some(deno_flags) = deno_flags {
|
||||
for flag in deno_flags {
|
||||
args.push(flag);
|
||||
}
|
||||
} else {
|
||||
args.push("-A");
|
||||
}
|
||||
args.push("/tmp/main.ts");
|
||||
|
||||
Command::new(nsjail_path)
|
||||
@@ -1496,13 +1565,18 @@ run();
|
||||
} else {
|
||||
let mut args = Vec::new();
|
||||
let script_path = format!("{job_dir}/main.ts");
|
||||
let import_map_path = format!("{job_dir}/import_map.json");
|
||||
args.push("run");
|
||||
if lockfile.is_some() {
|
||||
args.push("--lock=/tmp/lock.json");
|
||||
}
|
||||
args.push("--import-map");
|
||||
args.push(&import_map_path);
|
||||
args.push("--unstable");
|
||||
args.push("--v8-flags=--max-heap-size=2048");
|
||||
args.push("-A");
|
||||
if let Some(deno_flags) = deno_flags {
|
||||
for flag in deno_flags {
|
||||
args.push(flag);
|
||||
}
|
||||
} else {
|
||||
args.push("-A");
|
||||
}
|
||||
args.push(&script_path);
|
||||
Command::new(deno_path)
|
||||
.current_dir(job_dir)
|
||||
@@ -1521,7 +1595,7 @@ run();
|
||||
}
|
||||
.instrument(trace_span!("create_deno_jail"))
|
||||
.await?;
|
||||
handle_child(&job.id, db, logs, timeout, child).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
@@ -1532,7 +1606,7 @@ async fn create_args_and_out_file(
|
||||
job_dir: &str,
|
||||
) -> Result<(), Error> {
|
||||
let args = if let Some(args) = &job.args {
|
||||
Some(transform_json_value(client, &job.workspace_id, args.clone()).await?)
|
||||
Some(transform_json_value("args", client, &job.workspace_id, args.clone()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1549,7 +1623,9 @@ lazy_static! {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_python_job(
|
||||
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
|
||||
envs @ Envs { nsjail_path, python_path, path_env, .. }: &Envs,
|
||||
envs @ Envs {
|
||||
nsjail_path, python_path, path_env, max_log_size, additional_python_paths, ..
|
||||
}: &Envs,
|
||||
requirements_o: Option<String>,
|
||||
job_dir: &str,
|
||||
worker_dir: &str,
|
||||
@@ -1565,7 +1641,8 @@ async fn handle_python_job(
|
||||
) -> error::Result<serde_json::Value> {
|
||||
create_dependencies_dir(job_dir).await;
|
||||
|
||||
let mut additional_python_paths: Vec<String> = vec![];
|
||||
let mut additional_python_paths: Vec<String> =
|
||||
additional_python_paths.to_owned().unwrap_or_else(|| vec![]);
|
||||
|
||||
let requirements = match requirements_o {
|
||||
Some(r) => r,
|
||||
@@ -1672,12 +1749,23 @@ async fn handle_python_job(
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let spread = if sig.star_kwargs {
|
||||
"args = kwargs".to_string()
|
||||
} else {
|
||||
sig.args
|
||||
.into_iter()
|
||||
.map(|x| format!("args[\"{}\"] = kwargs[\"{}\"]", x.name, x.name))
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
let wrapper_content: String = format!(
|
||||
r#"
|
||||
import json
|
||||
{import_loader}
|
||||
{import_base64}
|
||||
{import_datetime}
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
inner_script = __import__("inner")
|
||||
|
||||
@@ -1686,11 +1774,21 @@ with open("args.json") as f:
|
||||
for k, v in list(kwargs.items()):
|
||||
if v == '<function call>':
|
||||
del kwargs[k]
|
||||
args = {{}}
|
||||
{spread}
|
||||
{transforms}
|
||||
res = inner_script.main(**kwargs)
|
||||
res_json = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')
|
||||
with open("result.json", 'w') as f:
|
||||
f.write(res_json)
|
||||
try:
|
||||
res = inner_script.main(**args)
|
||||
res_json = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')
|
||||
with open("result.json", 'w') as f:
|
||||
f.write(res_json)
|
||||
except Exception as e:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
tb = traceback.format_tb(exc_traceback)
|
||||
with open("result.json", 'w') as f:
|
||||
err_json = json.dumps({{ "message": str(e), "name": e.__class__.__name__, "stack": '\n'.join(tb[1:]) }}, separators=(',', ':'), default=str).replace('\n', '')
|
||||
f.write(err_json)
|
||||
sys.exit(1)
|
||||
"#,
|
||||
);
|
||||
write_file(job_dir, "main.py", &wrapper_content).await?;
|
||||
@@ -1770,7 +1868,7 @@ mount {{
|
||||
.spawn()?
|
||||
};
|
||||
|
||||
handle_child(&job.id, db, logs, timeout, child).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
@@ -1966,7 +2064,7 @@ async fn generate_deno_lock(
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
timeout: i32,
|
||||
Envs { deno_path, .. }: &Envs,
|
||||
Envs { deno_path, max_log_size, .. }: &Envs,
|
||||
) -> error::Result<String> {
|
||||
let _ = write_file(job_dir, "main.ts", code).await?;
|
||||
|
||||
@@ -1984,7 +2082,7 @@ async fn generate_deno_lock(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
handle_child(job_id, db, logs, timeout, child).await?;
|
||||
handle_child(job_id, db, logs, timeout, *max_log_size, child).await?;
|
||||
|
||||
let path_lock = format!("{job_dir}/lock.json");
|
||||
let mut file = File::open(path_lock).await?;
|
||||
@@ -2015,6 +2113,7 @@ async fn capture_dependency_job(
|
||||
job_dir,
|
||||
db,
|
||||
timeout,
|
||||
envs.max_log_size,
|
||||
&envs.go_path,
|
||||
false,
|
||||
false,
|
||||
@@ -2033,7 +2132,14 @@ async fn pip_compile(
|
||||
requirements: &str,
|
||||
logs: &mut String,
|
||||
job_dir: &str,
|
||||
Envs { pip_extra_index_url, pip_index_url, pip_trusted_host, .. }: &Envs,
|
||||
Envs {
|
||||
pip_extra_index_url,
|
||||
pip_index_url,
|
||||
pip_trusted_host,
|
||||
max_log_size,
|
||||
pip_local_dependencies,
|
||||
..
|
||||
}: &Envs,
|
||||
db: &Pool<Postgres>,
|
||||
timeout: i32,
|
||||
) -> error::Result<String> {
|
||||
@@ -2041,6 +2147,15 @@ async fn pip_compile(
|
||||
set_logs(logs, job_id, db).await;
|
||||
logs.push_str(&format!("\ncontent of requirements:\n{}", requirements));
|
||||
let file = "requirements.in";
|
||||
let requirements = if let Some(pip_local_dependencies) = pip_local_dependencies {
|
||||
let deps = pip_local_dependencies.clone();
|
||||
requirements
|
||||
.lines()
|
||||
.filter(|s| !deps.contains(&s.to_string()))
|
||||
.join("\n")
|
||||
} else {
|
||||
requirements.to_string()
|
||||
};
|
||||
write_file(job_dir, file, &requirements).await?;
|
||||
|
||||
let mut args = vec!["-q", "--no-header", file, "--resolver=backtracking"];
|
||||
@@ -2059,7 +2174,7 @@ async fn pip_compile(
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
handle_child(job_id, db, logs, timeout, child)
|
||||
handle_child(job_id, db, logs, timeout, *max_log_size, child)
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
|
||||
let path_lock = format!("{job_dir}/requirements.txt");
|
||||
@@ -2081,6 +2196,7 @@ async fn install_go_dependencies(
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
timeout: i32,
|
||||
max_log_size: i64,
|
||||
go_path: &str,
|
||||
preview: bool,
|
||||
skip_go_mod: bool,
|
||||
@@ -2094,7 +2210,7 @@ async fn install_go_dependencies(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
handle_child(job_id, db, logs, timeout, child).await?;
|
||||
handle_child(job_id, db, logs, timeout, max_log_size, child).await?;
|
||||
}
|
||||
let child = Command::new(go_path)
|
||||
.current_dir(job_dir)
|
||||
@@ -2102,7 +2218,7 @@ async fn install_go_dependencies(
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
handle_child(job_id, db, logs, timeout, child)
|
||||
handle_child(job_id, db, logs, timeout, max_log_size, child)
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
|
||||
|
||||
@@ -2184,6 +2300,7 @@ async fn handle_child(
|
||||
db: &Pool<Postgres>,
|
||||
logs: &mut String,
|
||||
timeout: i32,
|
||||
max_log_size: i64,
|
||||
mut child: Child,
|
||||
) -> error::Result<()> {
|
||||
let timeout = Duration::from_secs(u64::try_from(timeout).expect("invalid timeout"));
|
||||
@@ -2275,7 +2392,7 @@ async fn handle_child(
|
||||
/* a future that reads output from the child and appends to the database */
|
||||
let lines = async move {
|
||||
/* log_remaining is zero when output limit was reached */
|
||||
let mut log_remaining = (MAX_LOG_SIZE as usize).saturating_sub(logs.chars().count());
|
||||
let mut log_remaining = (max_log_size as usize).saturating_sub(logs.chars().count());
|
||||
let mut result = io::Result::Ok(());
|
||||
let mut output = output;
|
||||
/* `do_write` resolves the task, but does not contain the Result.
|
||||
@@ -2306,7 +2423,7 @@ async fn handle_child(
|
||||
tracing::info!(%job_id, "Too many logs lines for job {job_id}");
|
||||
let _ = set_too_many_logs.send(true);
|
||||
joined.push_str(&format!(
|
||||
"Job logs or result reached character limit of {MAX_LOG_SIZE}; killing job."
|
||||
"Job logs or result reached character limit of {max_log_size}; killing job."
|
||||
));
|
||||
/* stop reading and drop our streams fairly quickly */
|
||||
break;
|
||||
@@ -2390,18 +2507,21 @@ async fn handle_child(
|
||||
kill_tx.send(()).expect("send should always work");
|
||||
|
||||
match wait_result {
|
||||
_ if *too_many_logs.borrow() => Err(Error::ExecutionErr(
|
||||
"logs or result reached limit".to_string(),
|
||||
)),
|
||||
_ if *too_many_logs.borrow() => Err(Error::ExecutionErr(format!(
|
||||
"logs or result reached limit. Set MAX_LOG_SIZE higher (current: {max_log_size})"
|
||||
))),
|
||||
Ok(Ok(status)) => {
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else if let Some(code) = status.code() {
|
||||
Err(error::Error::ExitStatus(code))
|
||||
} else {
|
||||
Err(error::Error::ExecutionErr(
|
||||
"process terminated by signal".to_string(),
|
||||
))
|
||||
Err(error::Error::ExecutionErr(format!(
|
||||
"process terminated by signal: {:#?}, stopped_signal: {:#?}, core_dumped: {}",
|
||||
status.signal(),
|
||||
status.stopped_signal(),
|
||||
status.core_dumped()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Ok(Err(kill_reason)) => Err(Error::ExecutionErr(format!(
|
||||
@@ -2568,7 +2688,7 @@ async fn handle_zombie_jobs(db: &Pool<Postgres>, timeout: i32, base_url: &str) {
|
||||
&job.permissioned_as,
|
||||
"ephemeral-zombie-jobs",
|
||||
timeout * 2,
|
||||
&job.created_by,
|
||||
&job.email,
|
||||
)
|
||||
.await
|
||||
.expect("could not create job token");
|
||||
@@ -2601,6 +2721,7 @@ async fn handle_python_reqs(
|
||||
pip_extra_index_url,
|
||||
pip_trusted_host,
|
||||
nsjail_path,
|
||||
max_log_size,
|
||||
..
|
||||
}: &Envs,
|
||||
job: &QueuedJob,
|
||||
@@ -2695,7 +2816,7 @@ async fn handle_python_reqs(
|
||||
.spawn()?
|
||||
};
|
||||
|
||||
let child = handle_child(&job.id, db, logs, timeout, child).await;
|
||||
let child = handle_child(&job.id, db, logs, timeout, *max_log_size, child).await;
|
||||
tracing::info!(
|
||||
worker_name = %worker_name,
|
||||
job_id = %job.id,
|
||||
|
||||
@@ -303,7 +303,6 @@ pub async fn update_flow_status_after_job_completion(
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
tracing::info!("setting failure module for flow XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX {parent_module:?}");
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE queue
|
||||
@@ -403,7 +402,7 @@ pub async fn update_flow_status_after_job_completion(
|
||||
{
|
||||
true
|
||||
}
|
||||
false if has_failure_module(flow, &mut tx).await? => true,
|
||||
false if has_failure_module(flow, &mut tx).await? && !is_failure_step => true,
|
||||
false => false,
|
||||
};
|
||||
|
||||
@@ -428,15 +427,17 @@ pub async fn update_flow_status_after_job_completion(
|
||||
"Flow job canceled".to_string()
|
||||
} else if stop_early {
|
||||
format!("Flow job stopped early because of a stop early predicate returning true")
|
||||
} else if success {
|
||||
"Flow job completed with success".to_string()
|
||||
} else {
|
||||
"Flow job completed".to_string()
|
||||
"Flow job completed with error".to_string()
|
||||
};
|
||||
if flow_job.canceled {
|
||||
add_completed_job_error(
|
||||
db,
|
||||
&flow_job,
|
||||
logs,
|
||||
&canceled_job_to_result(&flow_job),
|
||||
canceled_job_to_result(&flow_job),
|
||||
metrics.clone(),
|
||||
)
|
||||
.await?;
|
||||
@@ -469,7 +470,7 @@ pub async fn update_flow_status_after_job_completion(
|
||||
db,
|
||||
&flow_job,
|
||||
"Unexpected error during flow chaining:\n".to_string(),
|
||||
err,
|
||||
json!({"message": err.to_string(), "name": "InternalError"}),
|
||||
metrics.clone(),
|
||||
)
|
||||
.await;
|
||||
@@ -687,13 +688,20 @@ async fn transform_input(
|
||||
}
|
||||
}
|
||||
|
||||
let lresult = last_result.clone();
|
||||
let error = if let Some(error) = lresult.as_object() {
|
||||
error.get("error")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for (key, val) in input_transforms.into_iter() {
|
||||
match val {
|
||||
InputTransform::Static { value: _ } => (),
|
||||
InputTransform::Javascript { expr } => {
|
||||
let flow_input = flow_args.clone().unwrap_or_else(|| json!({}));
|
||||
let previous_result = last_result.clone();
|
||||
let context = vec![
|
||||
let mut context = vec![
|
||||
("params".to_string(), json!(mapped)),
|
||||
("previous_result".to_string(), previous_result),
|
||||
("flow_input".to_string(), flow_input),
|
||||
@@ -705,6 +713,10 @@ async fn transform_input(
|
||||
("approvers".to_string(), json!(approvers.clone())),
|
||||
];
|
||||
|
||||
if error.is_some() {
|
||||
context.push(("error".to_string(), error.unwrap().clone()));
|
||||
}
|
||||
|
||||
let v = eval_timeout(
|
||||
expr.to_string(),
|
||||
context,
|
||||
@@ -976,7 +988,7 @@ async fn push_next_flow_job(
|
||||
let success = false;
|
||||
let skipped = false;
|
||||
let logs = "Timed out waiting to be resumed".to_string();
|
||||
let result = json!({ "error": logs });
|
||||
let result = json!({ "error": {"message": logs, "name": "SuspendedTimeout"}});
|
||||
let _uuid =
|
||||
add_completed_job(db, &flow_job, success, skipped, result, logs).await?;
|
||||
|
||||
@@ -1871,7 +1883,7 @@ async fn get_transform_context(
|
||||
&flow_job.permissioned_as,
|
||||
"transform-input",
|
||||
10,
|
||||
&flow_job.created_by,
|
||||
&flow_job.email,
|
||||
)
|
||||
.await?;
|
||||
//we need to commit asap otherwise the token won't be valid for auth to check outside of this transaction
|
||||
|
||||
@@ -12,7 +12,7 @@ import hub from "./hub.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
const VERSION = "v1.60.0";
|
||||
const VERSION = "v1.61.1";
|
||||
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -7,6 +7,12 @@ export {
|
||||
UserService, WorkspaceService
|
||||
} from './windmill-api/index.ts'
|
||||
|
||||
|
||||
// @ts-ignore: Otherwise BigInt is not supported for export
|
||||
BigInt.prototype.toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
export { pgSql, pgClient } from './pg.ts'
|
||||
export { mySql, mysqlClient } from './mysql.ts'
|
||||
|
||||
|
||||
1
depot.json
Normal file
1
depot.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "id": "00zvxx9420" }
|
||||
@@ -3,6 +3,9 @@ FROM ghcr.io/windmill-labs/windmill-ee
|
||||
RUN /usr/local/bin/python3 -m pip install nltk
|
||||
RUN mkdir -p /nsjail_data/python && HOME=/nsjail_data/python /usr/local/bin/python3 -m nltk.downloader vader_lexicon
|
||||
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - &&\
|
||||
apt-get install -y nodejs
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y tesseract-ocr \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
140
docker/DockerfileOpenbb
Normal file
140
docker/DockerfileOpenbb
Normal file
@@ -0,0 +1,140 @@
|
||||
FROM debian:buster-slim as nsjail
|
||||
|
||||
WORKDIR /nsjail
|
||||
|
||||
ARG nsjail=""
|
||||
|
||||
RUN if [ "$nsjail" = "true" ]; then apt-get -y update \
|
||||
&& apt-get install -y \
|
||||
bison=2:3.3.* \
|
||||
flex=2.6.* \
|
||||
g++=4:8.3.* \
|
||||
gcc=4:8.3.* \
|
||||
git=1:2.20.* \
|
||||
libprotobuf-dev=3.6.* \
|
||||
libnl-route-3-dev=3.4.* \
|
||||
make=4.2.* \
|
||||
pkg-config=0.29-6 \
|
||||
protobuf-compiler=3.6.*; fi
|
||||
|
||||
|
||||
RUN if [ "$nsjail" = "true" ]; then git clone -b master --single-branch https://github.com/google/nsjail.git . \
|
||||
&& git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800; fi
|
||||
RUN if [ "$nsjail" = "true" ]; then make; else touch nsjail; fi
|
||||
|
||||
FROM rust:slim-buster AS rust_base
|
||||
|
||||
RUN apt-get update && apt-get install -y git libssl-dev pkg-config npm
|
||||
|
||||
RUN apt-get -y update \
|
||||
&& apt-get install -y \
|
||||
curl lld nodejs npm
|
||||
|
||||
RUN rustup component add rustfmt
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo install cargo-chef
|
||||
|
||||
WORKDIR /windmill
|
||||
|
||||
ENV SQLX_OFFLINE=true
|
||||
ENV CARGO_INCREMENTAL=1
|
||||
|
||||
FROM node:19-alpine as frontend
|
||||
|
||||
# install dependencies
|
||||
WORKDIR /frontend
|
||||
COPY ./frontend/package.json ./frontend/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy all local files into the image.
|
||||
COPY frontend .
|
||||
RUN mkdir /backend
|
||||
COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml
|
||||
COPY /openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh
|
||||
RUN cd /backend/windmill-api && . ./build_openapi.sh
|
||||
|
||||
RUN npm run generate-backend-client
|
||||
ENV NODE_OPTIONS "--max-old-space-size=8192"
|
||||
RUN npm run build
|
||||
RUN npm run check
|
||||
|
||||
|
||||
|
||||
FROM rust_base AS planner
|
||||
|
||||
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY ./backend ./
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM rust_base AS builder
|
||||
ARG features=""
|
||||
|
||||
COPY --from=planner /windmill/recipe.json recipe.json
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef cook --release --features "$features" --recipe-path recipe.json
|
||||
|
||||
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
|
||||
COPY ./backend ./
|
||||
|
||||
COPY --from=frontend /frontend /frontend
|
||||
COPY --from=frontend /backend/windmill-api/openapi-deref.yaml ./windmill-api/openapi-deref.yaml
|
||||
COPY .git/ .git/
|
||||
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"
|
||||
|
||||
|
||||
FROM python:3.10.8-slim-buster
|
||||
|
||||
ARG APP=/usr/src/app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y ca-certificates wget curl git jq libprotobuf-dev libnl-route-3-dev unzip \
|
||||
&& apt-get install -y ca-certificates wget curl git jq libprotobuf-dev libnl-route-3-dev unzip build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
curl -o rclone.zip "https://downloads.rclone.org/v1.60.1/rclone-v1.60.1-linux-$arch.zip"; \
|
||||
unzip -p rclone.zip rclone-v1.60.1-linux-$arch/rclone > /usr/bin/rclone; rm rclone.zip; \
|
||||
chown root:root /usr/bin/rclone; chmod 755 /usr/bin/rclone
|
||||
|
||||
RUN set -eux; \
|
||||
arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
|
||||
url=; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
targz='go1.19.3.linux-amd64.tar.gz'; \
|
||||
;; \
|
||||
'arm64') \
|
||||
targz='go1.19.3.linux-arm64.tar.gz'; \
|
||||
;; \
|
||||
'armhf') \
|
||||
targz='go1.19.3.linux-armv6l.tar.gz'; \
|
||||
;; \
|
||||
*) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \
|
||||
esac; \
|
||||
wget "https://golang.org/dl/$targz" -nv && tar -C /usr/local -xzf "$targz" && rm "$targz";
|
||||
|
||||
ENV PATH="${PATH}:/usr/local/go/bin"
|
||||
ENV GO_PATH=/usr/local/go/bin/go
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
|
||||
COPY --from=builder /windmill/target/release/windmill ${APP}/windmill
|
||||
|
||||
COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
COPY --from=denoland/deno:latest /usr/bin/deno /usr/bin/deno
|
||||
|
||||
RUN /usr/local/bin/python3 -m pip install openbb[all]
|
||||
|
||||
RUN mkdir -p ${APP}
|
||||
|
||||
WORKDIR ${APP}
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["./windmill"]
|
||||
286
frontend/package-lock.json
generated
286
frontend/package-lock.json
generated
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.60.0",
|
||||
"version": "1.61.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.60.0",
|
||||
"version": "1.61.1",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@leeoniya/ufuzzy": "^0.9.0",
|
||||
"@leeoniya/ufuzzy": "^0.9.1",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.7.2",
|
||||
"@tanstack/svelte-table": "^8.7.6",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
@@ -24,6 +24,7 @@
|
||||
"monaco-yaml": "^4.0.2",
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
@@ -31,12 +32,12 @@
|
||||
"@playwright/test": "^1.29.2",
|
||||
"@sveltejs/adapter-static": "^1.0.0",
|
||||
"@sveltejs/kit": "^1.0.0-next.589",
|
||||
"@sveltejs/package": "^1.0.1",
|
||||
"@sveltejs/package": "^1.0.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
@@ -51,7 +52,7 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.1",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
@@ -70,7 +71,7 @@
|
||||
"tailwindcss": "^3.2.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.3"
|
||||
"vite": "^4.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.53.1"
|
||||
@@ -708,9 +709,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@leeoniya/ufuzzy": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz",
|
||||
"integrity": "sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg=="
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
|
||||
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
@@ -827,15 +828,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/package": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.1.tgz",
|
||||
"integrity": "sha512-iYoDz4AEJQUfdKUfBcwtYEGYkf4NMByQL3Sl2ESnu+IXsLNsHvhH0zUDhAmUmAgcrH8fVjiR7FuJeyh+7EQtiw==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.2.tgz",
|
||||
"integrity": "sha512-VY9U+05d9uNFDj7ScKRlHORYlfPSHwJewBjV+V2RsnViexpLFPUrboC9SiPYDCpLnbeqwXerxhO6twGHUBGeIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.3",
|
||||
"kleur": "^4.1.5",
|
||||
"sade": "^1.8.1",
|
||||
"svelte2tsx": "~0.5.20"
|
||||
"svelte2tsx": "~0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"svelte-package": "svelte-package.js"
|
||||
@@ -847,6 +848,20 @@
|
||||
"svelte": "^3.44.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/package/node_modules/svelte2tsx": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
|
||||
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.2.tgz",
|
||||
@@ -896,11 +911,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/svelte-table": {
|
||||
"version": "8.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.2.tgz",
|
||||
"integrity": "sha512-uAZQQXWnwuyT5H7uCyxUYlJIm+6jv34ibzLbuwwGAzvnJa6QZcBsQILAgO6hJYBHIt8PRA86uC5/w3sBpokH8g==",
|
||||
"version": "8.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.6.tgz",
|
||||
"integrity": "sha512-Nm4rgww6FnRwyvHZ16dzGQPMJOs6Z0DYmixHG6yfa7stAOtEXLRxREmdqiPxTlBTSDVaCcgJeYEyfwvVl7BH8w==",
|
||||
"dependencies": {
|
||||
"@tanstack/table-core": "8.7.2"
|
||||
"@tanstack/table-core": "8.7.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -914,9 +929,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/table-core": {
|
||||
"version": "8.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.2.tgz",
|
||||
"integrity": "sha512-Y+3AyuyFvjFlNdxbQ8Mm7muX5yUN31VSABDPyEpNsBIKyJre+AvqcsEdLoPGGAGtHsl+creH3xDOdn2ZPl19Cg==",
|
||||
"version": "8.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.6.tgz",
|
||||
"integrity": "sha512-sqiNTMzB6cpyL8DFH6/VqW48SwiflLqxQqYpo2wNock7rdVGvlm0BLNI8vZUJbr1+fmmWmHwBvi5OMgZw8n1DA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -1000,14 +1015,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz",
|
||||
"integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
|
||||
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "5.46.1",
|
||||
"@typescript-eslint/type-utils": "5.46.1",
|
||||
"@typescript-eslint/utils": "5.46.1",
|
||||
"@typescript-eslint/scope-manager": "5.49.0",
|
||||
"@typescript-eslint/type-utils": "5.49.0",
|
||||
"@typescript-eslint/utils": "5.49.0",
|
||||
"debug": "^4.3.4",
|
||||
"ignore": "^5.2.0",
|
||||
"natural-compare-lite": "^1.4.0",
|
||||
@@ -1134,13 +1149,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz",
|
||||
"integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
|
||||
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/visitor-keys": "5.46.1"
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"@typescript-eslint/visitor-keys": "5.49.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -1151,13 +1166,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz",
|
||||
"integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
|
||||
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "5.46.1",
|
||||
"@typescript-eslint/utils": "5.46.1",
|
||||
"@typescript-eslint/typescript-estree": "5.49.0",
|
||||
"@typescript-eslint/utils": "5.49.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
},
|
||||
@@ -1178,9 +1193,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz",
|
||||
"integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
|
||||
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -1191,13 +1206,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz",
|
||||
"integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
|
||||
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/visitor-keys": "5.46.1",
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"@typescript-eslint/visitor-keys": "5.49.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -1218,16 +1233,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz",
|
||||
"integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
|
||||
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.46.1",
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/typescript-estree": "5.46.1",
|
||||
"@typescript-eslint/scope-manager": "5.49.0",
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"@typescript-eslint/typescript-estree": "5.49.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"semver": "^7.3.7"
|
||||
@@ -1244,12 +1259,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz",
|
||||
"integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
|
||||
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1867,11 +1882,12 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/chartjs-adapter-date-fns": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz",
|
||||
"integrity": "sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
|
||||
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
|
||||
"peerDependencies": {
|
||||
"chart.js": "^3.0.0"
|
||||
"chart.js": ">=2.8.0",
|
||||
"date-fns": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-plugin-zoom": {
|
||||
@@ -5158,9 +5174,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
|
||||
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==",
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
|
||||
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
@@ -6256,6 +6272,11 @@
|
||||
"svelte": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-portal": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.0.tgz",
|
||||
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
|
||||
},
|
||||
"node_modules/svelte-preprocess": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
|
||||
@@ -6780,9 +6801,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
||||
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
|
||||
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.16.3",
|
||||
@@ -7531,9 +7552,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@leeoniya/ufuzzy": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.0.tgz",
|
||||
"integrity": "sha512-p2zWsX0GwO1x723Yhb3KLAoSwp1geQvzRPHgIoOR/0qn8Ptpsb3b01+W47iAYR/NWo0pX36XQoTU0alVRykMAg=="
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
|
||||
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
@@ -7618,15 +7639,27 @@
|
||||
}
|
||||
},
|
||||
"@sveltejs/package": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.1.tgz",
|
||||
"integrity": "sha512-iYoDz4AEJQUfdKUfBcwtYEGYkf4NMByQL3Sl2ESnu+IXsLNsHvhH0zUDhAmUmAgcrH8fVjiR7FuJeyh+7EQtiw==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-1.0.2.tgz",
|
||||
"integrity": "sha512-VY9U+05d9uNFDj7ScKRlHORYlfPSHwJewBjV+V2RsnViexpLFPUrboC9SiPYDCpLnbeqwXerxhO6twGHUBGeIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": "^3.5.3",
|
||||
"kleur": "^4.1.5",
|
||||
"sade": "^1.8.1",
|
||||
"svelte2tsx": "~0.5.20"
|
||||
"svelte2tsx": "~0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte2tsx": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
|
||||
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sveltejs/vite-plugin-svelte": {
|
||||
@@ -7665,17 +7698,17 @@
|
||||
}
|
||||
},
|
||||
"@tanstack/svelte-table": {
|
||||
"version": "8.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.2.tgz",
|
||||
"integrity": "sha512-uAZQQXWnwuyT5H7uCyxUYlJIm+6jv34ibzLbuwwGAzvnJa6QZcBsQILAgO6hJYBHIt8PRA86uC5/w3sBpokH8g==",
|
||||
"version": "8.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/svelte-table/-/svelte-table-8.7.6.tgz",
|
||||
"integrity": "sha512-Nm4rgww6FnRwyvHZ16dzGQPMJOs6Z0DYmixHG6yfa7stAOtEXLRxREmdqiPxTlBTSDVaCcgJeYEyfwvVl7BH8w==",
|
||||
"requires": {
|
||||
"@tanstack/table-core": "8.7.2"
|
||||
"@tanstack/table-core": "8.7.6"
|
||||
}
|
||||
},
|
||||
"@tanstack/table-core": {
|
||||
"version": "8.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.2.tgz",
|
||||
"integrity": "sha512-Y+3AyuyFvjFlNdxbQ8Mm7muX5yUN31VSABDPyEpNsBIKyJre+AvqcsEdLoPGGAGtHsl+creH3xDOdn2ZPl19Cg=="
|
||||
"version": "8.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.7.6.tgz",
|
||||
"integrity": "sha512-sqiNTMzB6cpyL8DFH6/VqW48SwiflLqxQqYpo2wNock7rdVGvlm0BLNI8vZUJbr1+fmmWmHwBvi5OMgZw8n1DA=="
|
||||
},
|
||||
"@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
@@ -7749,14 +7782,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz",
|
||||
"integrity": "sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
|
||||
"integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/scope-manager": "5.46.1",
|
||||
"@typescript-eslint/type-utils": "5.46.1",
|
||||
"@typescript-eslint/utils": "5.46.1",
|
||||
"@typescript-eslint/scope-manager": "5.49.0",
|
||||
"@typescript-eslint/type-utils": "5.49.0",
|
||||
"@typescript-eslint/utils": "5.49.0",
|
||||
"debug": "^4.3.4",
|
||||
"ignore": "^5.2.0",
|
||||
"natural-compare-lite": "^1.4.0",
|
||||
@@ -7821,41 +7854,41 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.1.tgz",
|
||||
"integrity": "sha512-iOChVivo4jpwUdrJZyXSMrEIM/PvsbbDOX1y3UCKjSgWn+W89skxWaYXACQfxmIGhPVpRWK/VWPYc+bad6smIA==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
|
||||
"integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/visitor-keys": "5.46.1"
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"@typescript-eslint/visitor-keys": "5.49.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/type-utils": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.46.1.tgz",
|
||||
"integrity": "sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
|
||||
"integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/typescript-estree": "5.46.1",
|
||||
"@typescript-eslint/utils": "5.46.1",
|
||||
"@typescript-eslint/typescript-estree": "5.49.0",
|
||||
"@typescript-eslint/utils": "5.49.0",
|
||||
"debug": "^4.3.4",
|
||||
"tsutils": "^3.21.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.1.tgz",
|
||||
"integrity": "sha512-Z5pvlCaZgU+93ryiYUwGwLl9AQVB/PQ1TsJ9NZ/gHzZjN7g9IAn6RSDkpCV8hqTwAiaj6fmCcKSQeBPlIpW28w==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
|
||||
"integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.1.tgz",
|
||||
"integrity": "sha512-j9W4t67QiNp90kh5Nbr1w92wzt+toiIsaVPnEblB2Ih2U9fqBTyqV9T3pYWZBRt6QoMh/zVWP59EpuCjc4VRBg==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
|
||||
"integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/visitor-keys": "5.46.1",
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"@typescript-eslint/visitor-keys": "5.49.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -7864,28 +7897,28 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/utils": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.46.1.tgz",
|
||||
"integrity": "sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
|
||||
"integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@typescript-eslint/scope-manager": "5.46.1",
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/typescript-estree": "5.46.1",
|
||||
"@typescript-eslint/scope-manager": "5.49.0",
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"@typescript-eslint/typescript-estree": "5.49.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-utils": "^3.0.0",
|
||||
"semver": "^7.3.7"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.1.tgz",
|
||||
"integrity": "sha512-jczZ9noovXwy59KjRTk1OftT78pwygdcmCuBf8yMoWt/8O8l+6x2LSEze0E4TeepXK4MezW3zGSyoDRZK7Y9cg==",
|
||||
"version": "5.49.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
|
||||
"integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "5.46.1",
|
||||
"@typescript-eslint/types": "5.49.0",
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
@@ -8308,9 +8341,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"chartjs-adapter-date-fns": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz",
|
||||
"integrity": "sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
|
||||
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
|
||||
"requires": {}
|
||||
},
|
||||
"chartjs-plugin-zoom": {
|
||||
@@ -10715,9 +10748,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz",
|
||||
"integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg=="
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
|
||||
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw=="
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"version": "2.9.0",
|
||||
@@ -11548,6 +11581,11 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"svelte-portal": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.0.tgz",
|
||||
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
|
||||
},
|
||||
"svelte-preprocess": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
|
||||
@@ -11899,9 +11937,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
||||
"integrity": "sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz",
|
||||
"integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.16.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.60.0",
|
||||
"version": "1.61.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -18,12 +18,12 @@
|
||||
"@playwright/test": "^1.29.2",
|
||||
"@sveltejs/adapter-static": "^1.0.0",
|
||||
"@sveltejs/kit": "^1.0.0-next.589",
|
||||
"@sveltejs/package": "^1.0.1",
|
||||
"@sveltejs/package": "^1.0.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
@@ -38,7 +38,7 @@
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.1",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
@@ -57,17 +57,17 @@
|
||||
"tailwindcss": "^3.2.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.3"
|
||||
"vite": "^4.0.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@leeoniya/ufuzzy": "^0.9.0",
|
||||
"@leeoniya/ufuzzy": "^0.9.1",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.7.2",
|
||||
"@tanstack/svelte-table": "^8.7.6",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
@@ -77,6 +77,7 @@
|
||||
"monaco-yaml": "^4.0.2",
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface SchemaProperty {
|
||||
}
|
||||
|
||||
export type Schema = {
|
||||
$schema: string
|
||||
$schema: string | undefined
|
||||
type: string
|
||||
properties: { [name: string]: SchemaProperty }
|
||||
required: string[]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { emptySchema, emptyString } from '$lib/utils'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
|
||||
export let resource_type: string
|
||||
export let args: Record<string, any> | any = {}
|
||||
@@ -12,7 +13,10 @@
|
||||
|
||||
let schema = emptySchema()
|
||||
let notFound = false
|
||||
|
||||
async function loadSchema() {
|
||||
rawCode = '{}'
|
||||
viewJsonSchema = false
|
||||
try {
|
||||
const rt = await ResourceService.getResourceType({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -21,8 +25,6 @@
|
||||
schema = rt.schema
|
||||
notFound = false
|
||||
} catch (e) {
|
||||
rawCode = '{}'
|
||||
|
||||
notFound = true
|
||||
}
|
||||
}
|
||||
@@ -43,17 +45,40 @@
|
||||
}
|
||||
let error = ''
|
||||
let rawCode = ''
|
||||
let viewJsonSchema = false
|
||||
|
||||
$: rawCode && parseJson()
|
||||
|
||||
function switchTab(asJson: boolean) {
|
||||
viewJsonSchema = asJson
|
||||
if (asJson) {
|
||||
rawCode = JSON.stringify(args, null, 2)
|
||||
} else {
|
||||
parseJson()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if notFound}
|
||||
{#if !notFound}
|
||||
<div class="w-full flex flex-row-reverse">
|
||||
<Toggle
|
||||
on:change={(e) => switchTab(e.detail)}
|
||||
options={{
|
||||
right: 'As JSON'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="italic text-gray-500 text-xs mb-4"
|
||||
>No corresponding resource type found in your workspace for {resource_type}. Define the value in
|
||||
JSON directly</p
|
||||
>
|
||||
{/if}
|
||||
{#if notFound || viewJsonSchema}
|
||||
{#if !emptyString(error)}<span class="text-red-400 text-xs mb-1 flex flex-row-reverse"
|
||||
>{error}</span
|
||||
>{:else}<div class="py-2" />{/if}
|
||||
<SimpleEditor autoHeight lang="json" bind:code={rawCode} fixedOverflowWidgets={false} />
|
||||
{:else}
|
||||
<SchemaForm {password} isValid {schema} bind:args />
|
||||
<SchemaForm noDelete {password} isValid {schema} bind:args />
|
||||
{/if}
|
||||
|
||||
@@ -1,35 +1,48 @@
|
||||
<script lang="ts" context="module">
|
||||
const apiTokenApps: Record<string, { img?: string; instructions: string[]; key?: string }> = {
|
||||
airtable: {
|
||||
img: 'airtable_connect.png',
|
||||
img: '/airtable_connect.png',
|
||||
instructions: ['Click on the top-right avatar', 'Click on Account', 'Find "Api"']
|
||||
},
|
||||
discord_webhook: {
|
||||
img: 'discord_webhook.png',
|
||||
img: '/discord_webhook.png',
|
||||
instructions: ['Click on Server Settings', 'Click on Integration', 'Find "Webhooks"'],
|
||||
key: 'webhook_url'
|
||||
},
|
||||
toggl: {
|
||||
img: 'toggl_connect.png',
|
||||
img: '/toggl_connect.png',
|
||||
instructions: [
|
||||
'Go to <a href="https://track.toggl.com/profile" target="_blank" rel=”noopener noreferrer”>https://track.toggl.com/profile</a>',
|
||||
'Find "API Token"'
|
||||
]
|
||||
},
|
||||
mailchimp: {
|
||||
img: 'mailchimp_connect.png',
|
||||
img: '/mailchimp_connect.png',
|
||||
instructions: [
|
||||
'Go to <a href="https://admin.mailchimp.com/account/api" target="_blank" rel=”noopener noreferrer”>https://admin.mailchimp.com/account/api</a>',
|
||||
'Find "Your API Keys"'
|
||||
]
|
||||
},
|
||||
sendgrid: {
|
||||
img: 'sendgrid_connect.png',
|
||||
img: '/sendgrid_connect.png',
|
||||
instructions: [
|
||||
'Go to <a href="https://app.sendgrid.com/settings/api_keys" target="_blank" rel=”noopener noreferrer”>https://app.sendgrid.com/settings/api_keys</a>',
|
||||
'Create an API key',
|
||||
'Copy your key'
|
||||
]
|
||||
},
|
||||
supabase: {
|
||||
img: '/supabase_connect.png',
|
||||
instructions: ['Go to the API Settings of your app to find the project URL and key']
|
||||
},
|
||||
|
||||
square: {
|
||||
img: '/square_connect.gif',
|
||||
instructions: [
|
||||
'Go to <a href="https://developer.squareup.com/apps" target="_blank" rel=”noopener noreferrer”>https://developer.squareup.com/apps</a>',
|
||||
'In the left pane, choose Credentials',
|
||||
'At the top of the page, choose Production mode for a production access token or Sandbox mode for a Sandbox access token.'
|
||||
]
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -126,6 +139,7 @@
|
||||
const availableRts = await ResourceService.listResourceTypeNames({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
|
||||
connectsManual = availableRts
|
||||
.filter((x) => !Object.keys(connects ?? {}).includes(x))
|
||||
.map((x) => [
|
||||
@@ -141,6 +155,7 @@
|
||||
async function next() {
|
||||
if (step == 1 && manual) {
|
||||
step += 1
|
||||
args = {}
|
||||
} else if (step == 1 && !manual) {
|
||||
const url = new URL(`/api/oauth/connect/${resource_type}`, $page.url.origin)
|
||||
url.searchParams.append('scopes', scopes.join('+'))
|
||||
@@ -383,9 +398,6 @@
|
||||
namePlaceholder="my_{resource_type}"
|
||||
kind="resource"
|
||||
/>
|
||||
<h2 class="mt-4 mb-2">Description</h2>
|
||||
|
||||
<textarea type="text" autocomplete="off" use:autosize bind:value={description} />
|
||||
|
||||
{#if apiTokenApps[resource_type]}
|
||||
<h2 class="mt-4 mb-2">Instructions</h2>
|
||||
@@ -399,16 +411,21 @@
|
||||
</ol>
|
||||
</div>
|
||||
{#if apiTokenApps[resource_type].img}
|
||||
<div class="mt-4 w-full">
|
||||
<img class="max-h-96 m-auto" alt="connect" src={apiTokenApps[resource_type].img} />
|
||||
<div class="mt-4 w-full overflow-hidden">
|
||||
<img class="m-auto max-h-60" alt="connect" src={apiTokenApps[resource_type].img} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<h2 class="mt-8">Value</h2>
|
||||
<h2 class="mt-4">Value</h2>
|
||||
<div class="mt-4">
|
||||
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
|
||||
{#key resource_type}
|
||||
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<h2 class="mt-4 mb-2">Description</h2>
|
||||
<textarea type="text" autocomplete="off" use:autosize bind:value={description} />
|
||||
{:else}
|
||||
<Path
|
||||
initialPath=""
|
||||
|
||||
@@ -157,7 +157,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
export let inputCat: InputCat = 'string'
|
||||
$: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
import autosize from 'svelte-autosize'
|
||||
|
||||
export let result: any
|
||||
export let requireHtmlApproval = false
|
||||
|
||||
let resultKind:
|
||||
| 'json'
|
||||
| 'table-col'
|
||||
| 'table-row'
|
||||
| 'html'
|
||||
| 'png'
|
||||
| 'file'
|
||||
| 'jpeg'
|
||||
@@ -19,9 +21,11 @@
|
||||
| 'error'
|
||||
| 'approval'
|
||||
| undefined
|
||||
|
||||
$: resultKind = inferResultKind(result)
|
||||
|
||||
let forceJson = false
|
||||
let enableHtml = false
|
||||
|
||||
function isRectangularArray(obj: any) {
|
||||
if (!Array.isArray(obj) || obj.length == 0) {
|
||||
@@ -53,13 +57,15 @@
|
||||
return 'table-row'
|
||||
} else if (keys.map((k) => Array.isArray(result[k])).reduce((a, b) => a && b)) {
|
||||
return 'table-col'
|
||||
} else if (keys.length == 1 && keys[0] == 'html') {
|
||||
return 'html'
|
||||
} else if (keys.length == 1 && keys[0] == 'png') {
|
||||
return 'png'
|
||||
} else if (keys.length == 1 && keys[0] == 'jpeg') {
|
||||
return 'jpeg'
|
||||
} else if (keys.length == 1 && keys[0] == 'file') {
|
||||
return 'file'
|
||||
} else if (keys.length == 1 && keys[0] == 'error' && typeof result['error'] == 'string') {
|
||||
} else if (keys.length == 1 && keys[0] == 'error') {
|
||||
return 'error'
|
||||
} else if (
|
||||
keys.length == 3 &&
|
||||
@@ -96,7 +102,7 @@
|
||||
</div>
|
||||
{#if Array.isArray(result[col])}
|
||||
{#each result[col] as item}
|
||||
<div class="px-12 text-left whitespace-nowrap">
|
||||
<div class="px-12 text-left text-xs whitespace-nowrap">
|
||||
{typeof item === 'string' ? item : JSON.stringify(item)}
|
||||
</div>
|
||||
{/each}
|
||||
@@ -105,20 +111,48 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'table-row'}<div
|
||||
class="grid grid-flow-col-dense border border-gray-200 rounded-md "
|
||||
class="grid grid-flow-col-dense border border-gray-200 "
|
||||
>
|
||||
<TableCustom>
|
||||
<tbody slot="body">
|
||||
{#each asListOfList(result) as row}
|
||||
<tr>
|
||||
{#each row as v}
|
||||
<td>{truncate(JSON.stringify(v), 200) ?? ''}</td>
|
||||
<td class="!text-xs">{truncate(JSON.stringify(v), 200) ?? ''}</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'html'}
|
||||
<div class="h-full">
|
||||
{#if !requireHtmlApproval || enableHtml}
|
||||
{@html result.html}
|
||||
{:else}
|
||||
<div class="font-main text-sm">
|
||||
<div class="flex flex-col">
|
||||
<div class="bg-red-400 py-1 rounded-t text-white font-bold text-center">
|
||||
Warning
|
||||
</div>
|
||||
<p
|
||||
class="text-gray-600 mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
|
||||
>Rendering HTML can expose you to <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noreferrer" class="hover:underline">XSS attacks</a>.
|
||||
Only enable it if you trust the author of the script.
|
||||
</p>
|
||||
</div>
|
||||
<div class="center-center">
|
||||
<Button
|
||||
size="sm"
|
||||
color="dark"
|
||||
on:click={() => enableHtml = true}
|
||||
>
|
||||
Enable HTML rendering
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'png'}
|
||||
<div class="h-full"
|
||||
><img alt="png rendered" class="w-auto h-full" src="data:image/png;base64,{result.png}" />
|
||||
@@ -141,8 +175,11 @@
|
||||
>Download</a
|
||||
>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'error'}<div
|
||||
><pre class="text-sm text-red-500 whitespace-pre-wrap">{result.error}</pre>
|
||||
{:else if !forceJson && resultKind == 'error'}<div>
|
||||
<span class="text-red-500 font-semibold text-sm whitespace-pre-wrap"
|
||||
>{result.error.name}: {result.error.message}</span
|
||||
>
|
||||
<pre class="text-sm whitespace-pre-wrap text-gray-900">{result.error.stack ?? ''}</pre>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'approval'}<div class="flex flex-col gap-1 mx-4">
|
||||
<Button
|
||||
|
||||
@@ -411,7 +411,8 @@
|
||||
|
||||
model.updateOptions(updateOptions)
|
||||
editor = meditor.create(divEl as HTMLDivElement, {
|
||||
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets)
|
||||
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
tabSize: lang == 'python' ? 4 : 2
|
||||
})
|
||||
|
||||
let timeoutModel: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import LogViewer from './LogViewer.svelte'
|
||||
|
||||
@@ -6,6 +7,7 @@
|
||||
export let logs: string
|
||||
export let col: boolean = false
|
||||
export let noBorder = false
|
||||
export let loading
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -14,7 +16,13 @@
|
||||
>
|
||||
<div class="bg-white max-h-80 h-full p-1 overflow-auto relative">
|
||||
<span class="text-gray-500">Result</span>
|
||||
<DisplayResult {result} />
|
||||
{#if result}
|
||||
<DisplayResult {result} />
|
||||
{:else if loading}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:else}
|
||||
<div class="text-gray-400">No result (result is undefined)</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="overflow-auto max-h-80 h-full relative">
|
||||
<LogViewer content={logs ?? ''} isLoading={false} />
|
||||
|
||||
@@ -12,6 +12,19 @@
|
||||
faBarsStaggered
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import ScheduleEditor from './ScheduleEditor.svelte'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
let time = Date.now()
|
||||
let interval
|
||||
onMount(() => {
|
||||
interval = setInterval(() => {
|
||||
time = Date.now()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
interval && clearInterval(interval)
|
||||
})
|
||||
|
||||
export let job: Job
|
||||
const SMALL_ICON_SCALE = 0.7
|
||||
@@ -23,14 +36,19 @@
|
||||
<div class="rounded-md p-3 bg-gray-50 shadow-sm sm:text-sm md:text-base" style="min-height: 150px;">
|
||||
<JobStatus {job} />
|
||||
<div>
|
||||
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span class="mx-2">
|
||||
Created {displayDaysAgo(job.created_at ?? '')}</span
|
||||
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span
|
||||
class="mx-2 text-2xs text-gray-600"
|
||||
>
|
||||
{#key time}
|
||||
Received job {displayDaysAgo(job.created_at ?? '')}
|
||||
{/key}</span
|
||||
>
|
||||
</div>
|
||||
{#if job && 'started_at' in job && job.started_at}
|
||||
<div>
|
||||
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span class="mx-2">
|
||||
Started {displayDaysAgo(job.started_at ?? '')}</span
|
||||
{#key time}
|
||||
Started {displayDaysAgo(job.started_at ?? '')}{/key}</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import FlowProgressBar from './flows/FlowProgressBar.svelte'
|
||||
import { flowStateStore } from './flows/flowState'
|
||||
import CapturePayload from './flows/content/CapturePayload.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
let capturePayload: CapturePayload
|
||||
export let previewMode: 'upTo' | 'whole'
|
||||
@@ -119,8 +120,9 @@
|
||||
jobId = undefined
|
||||
}}
|
||||
size="md"
|
||||
btnClasses="w-full"
|
||||
btnClasses="w-full max-w-lg"
|
||||
>
|
||||
<Loader2 class="animate-spin mr-2" />
|
||||
Cancel
|
||||
</Button>
|
||||
{:else}
|
||||
@@ -129,7 +131,7 @@
|
||||
startIcon={{ icon: isRunning ? faRefresh : faPlay }}
|
||||
color="blue"
|
||||
size="sm"
|
||||
btnClasses="w-full"
|
||||
btnClasses="w-full max-w-lg"
|
||||
disabled={!isValid}
|
||||
on:click={() => runPreview($previewArgs)}
|
||||
>
|
||||
@@ -147,15 +149,18 @@
|
||||
</div>
|
||||
<FlowProgressBar {job} bind:reset={jobProgressReset} />
|
||||
|
||||
<div class="overflow-y-auto grow flex-col flex divide-y divide-gray-600 ">
|
||||
<SchemaForm
|
||||
compact
|
||||
class="py-4"
|
||||
schema={$flowStore.schema}
|
||||
bind:isValid
|
||||
bind:args={$previewArgs}
|
||||
/>
|
||||
<div class="h-full pt-4 grow">
|
||||
<div class="overflow-y-auto grow divide-y divide-gray-600 ">
|
||||
<div class="max-h-1/2 overflow-auto border-b border-gray-700">
|
||||
<SchemaForm
|
||||
noVariablePicker
|
||||
compact
|
||||
class="py-4 max-w-3xl"
|
||||
schema={$flowStore.schema}
|
||||
bind:isValid
|
||||
bind:args={$previewArgs}
|
||||
/>
|
||||
</div>
|
||||
<div class="pt-4 grow">
|
||||
{#if jobId}
|
||||
<FlowStatusViewer bind:flowState={$flowStateStore} {jobId} bind:job />
|
||||
{:else}
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import type { FlowState } from './flows/flowState'
|
||||
import { Button, Tab } from './common'
|
||||
import { Badge, Button, Tab } from './common'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import Tabs from './common/tabs/Tabs.svelte'
|
||||
import { FlowGraph, type GraphModuleState } from './graph'
|
||||
import ModuleStatus from './ModuleStatus.svelte'
|
||||
import { displayDate, isOwner, pluralize, truncateRev } from '$lib/utils'
|
||||
import { displayDate, emptyString, isOwner, pluralize, truncateRev } from '$lib/utils'
|
||||
import JobArgs from './JobArgs.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -179,7 +180,11 @@
|
||||
<FlowPreviewStatus {job} />
|
||||
{#if `result` in job}
|
||||
<div class="w-full h-full">
|
||||
<FlowJobResult result={job.result} logs={job.logs ?? ''} />
|
||||
<FlowJobResult
|
||||
loading={job['running'] == true}
|
||||
result={job.result}
|
||||
logs={job.logs ?? ''}
|
||||
/>
|
||||
</div>
|
||||
{:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS}
|
||||
<div class="w-full h-full mt-2 text-sm text-gray-600">
|
||||
@@ -221,6 +226,31 @@
|
||||
<div class="text-xs p-4 bg-gray-50 overflow-auto max-h-80 border">
|
||||
<pre class="w-full">{job.logs}</pre>
|
||||
</div>
|
||||
{:else if innerModules?.length > 0}
|
||||
<div class="flex flex-col gap-1">
|
||||
{#each innerModules as mod, i (mod.id)}
|
||||
{#if mod.type == FlowStatusModule.type.IN_PROGRESS}
|
||||
{@const rawMod = job.raw_flow?.modules[i]}
|
||||
|
||||
<div
|
||||
><span class="inline-flex gap-1"
|
||||
><Badge color="indigo">{mod.id}</Badge>
|
||||
<span class="font-medium text-gray-900">
|
||||
{#if !emptyString(rawMod?.summary)}
|
||||
{rawMod?.summary ?? ''}
|
||||
{:else if rawMod?.value.type == 'script'}
|
||||
{rawMod.value.path ?? ''}
|
||||
{:else if rawMod?.value.type}
|
||||
{rawMod?.value.type}
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<Loader2 class="animate-spin" /></span
|
||||
></div
|
||||
>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -425,7 +455,13 @@
|
||||
{#if selectedNode}
|
||||
{@const node = localFlowModuleStates[selectedNode]}
|
||||
{#if selectedNode == 'end'}
|
||||
<FlowJobResult noBorder col result={job['result'] ?? {}} logs={job.logs ?? ''} />
|
||||
<FlowJobResult
|
||||
loading={job['running']}
|
||||
noBorder
|
||||
col
|
||||
result={job['result']}
|
||||
logs={job.logs ?? ''}
|
||||
/>
|
||||
{:else if selectedNode == 'start'}
|
||||
{#if job.args}
|
||||
<div class="p-2">
|
||||
@@ -452,7 +488,13 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<FlowJobResult noBorder col result={node.result ?? {}} logs={node.logs ?? ''} />
|
||||
<FlowJobResult
|
||||
loading={job['running'] == true}
|
||||
noBorder
|
||||
col
|
||||
result={node.result}
|
||||
logs={node.logs ?? ''}
|
||||
/>
|
||||
{:else}
|
||||
<p class="p-2 text-gray-600 italic"
|
||||
>The execution of this node has no information attached to it. The job likely did
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
{#if perms}
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th>user</th>
|
||||
<th>user/group</th>
|
||||
<th />
|
||||
<th />
|
||||
</tr>
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
export let width = '24px'
|
||||
export let center = false
|
||||
|
||||
$: name_prefix = name.split('_')[0]
|
||||
$: iconComponent = APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
|
||||
</script>
|
||||
|
||||
<div class="truncate flex flex-row gap-2 {center ? 'justify-center items-center' : ''} -pl-2">
|
||||
{#if !silent && !after}
|
||||
{name}
|
||||
{/if}
|
||||
{#if APP_TO_ICON_COMPONENT[name_prefix]}
|
||||
{#if iconComponent}
|
||||
<span class="text-gray-700 grayscale">
|
||||
<svelte:component this={APP_TO_ICON_COMPONENT[name_prefix]} {height} {width} />
|
||||
<svelte:component this={iconComponent} {height} {width} />
|
||||
</span>
|
||||
{:else}
|
||||
<span style="width: {width}; height: {height}" class="bg-gray-100 rounded-full" />
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
let monacoTemplate: TemplateEditor | undefined = undefined
|
||||
let argInput: ArgInput | undefined = undefined
|
||||
|
||||
let inputCat: InputCat = computeInputCat(
|
||||
$: inputCat = computeInputCat(
|
||||
schema.properties[argName].type,
|
||||
schema.properties[argName].format,
|
||||
schema.properties[argName].items?.type,
|
||||
@@ -60,7 +60,11 @@
|
||||
}
|
||||
|
||||
if (isCodeInjection(rawValue)) {
|
||||
arg.expr = getDefaultExpr(argName, previousModuleId, `\`${rawValue}\``)
|
||||
arg.expr = getDefaultExpr(
|
||||
argName,
|
||||
previousModuleId,
|
||||
`\`${rawValue.toString().replaceAll('`', '\\`')}\``
|
||||
)
|
||||
arg.type = 'javascript'
|
||||
propertyType = 'static'
|
||||
} else {
|
||||
@@ -117,9 +121,13 @@
|
||||
const openBracket = '${'
|
||||
const closeBracket = '}'
|
||||
|
||||
$: schema.properties[argName].default &&
|
||||
!arg?.value &&
|
||||
monacoTemplate?.setCode(schema.properties[argName].default)
|
||||
function setDefaultCode() {
|
||||
if (!arg?.value) {
|
||||
monacoTemplate?.setCode(schema.properties[argName].default)
|
||||
}
|
||||
}
|
||||
|
||||
$: schema.properties[argName].default && setDefaultCode()
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
@@ -157,7 +165,7 @@
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate
|
||||
? `\`${arg?.value ?? ''}\``
|
||||
? `\`${arg?.value.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
: arg.value
|
||||
? JSON.stringify(arg?.value, null, 4)
|
||||
: ''
|
||||
@@ -172,6 +180,7 @@
|
||||
if (staticTemplate) {
|
||||
if (arg) {
|
||||
arg.value = codeToStaticTemplate(arg.expr)
|
||||
arg.expr = undefined
|
||||
}
|
||||
setPropertyType(arg?.value)
|
||||
} else {
|
||||
@@ -269,7 +278,6 @@
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
properties={schema.properties[argName].properties}
|
||||
displayHeader={false}
|
||||
bind:inputCat
|
||||
{variableEditor}
|
||||
{itemPicker}
|
||||
bind:pickForField
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</tr>
|
||||
{/each}
|
||||
{:else if args}
|
||||
<tr>No arguments</tr>
|
||||
<tr><div class="text-gray-600 pt-2 pl-1 text-sm">No arguments</div></tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import { ScriptService, type FlowModule, type Job } from '$lib/gen'
|
||||
import { ScriptService, type FlowModule, type InputTransform, type Job } from '$lib/gen'
|
||||
import { getScriptByPath, sendUserToast, truncateRev } from '$lib/utils'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import RunForm from './RunForm.svelte'
|
||||
@@ -8,7 +8,7 @@
|
||||
import LogViewer from './LogViewer.svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { flowStateStore } from './flows/flowState'
|
||||
import { flowStateStore, testStepStore } from './flows/flowState'
|
||||
import { flowStore } from './flows/flowStore'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -21,7 +21,18 @@
|
||||
let testIsLoading = false
|
||||
let testJob: Job | undefined = undefined
|
||||
|
||||
let stepArgs: Record<string, any> = {}
|
||||
let stepArgs: Record<string, any> | undefined =
|
||||
$testStepStore[mod.id] ??
|
||||
Object.entries(mod.value['input_transforms'] ?? {}).reduce((acc, [k, v]) => {
|
||||
let t = v as InputTransform
|
||||
if (t.type == 'static') {
|
||||
acc[k] = t.value
|
||||
return acc
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
$: $testStepStore[mod.id] = stepArgs
|
||||
|
||||
export function runTestWithStepArgs() {
|
||||
runTest(stepArgs)
|
||||
@@ -66,11 +77,12 @@
|
||||
{/if}
|
||||
|
||||
<RunForm
|
||||
noVariablePicker
|
||||
loading={testIsLoading}
|
||||
runnable={{ summary: mod.summary ?? '', schema, description: '' }}
|
||||
runAction={(_, args) => runTest(args)}
|
||||
schedulable={false}
|
||||
buttonText="Test just this step (Ctrl+Enter)"
|
||||
buttonText="Test (Ctrl+Enter)"
|
||||
detailed={false}
|
||||
topButton
|
||||
bind:args={stepArgs}
|
||||
|
||||
3
frontend/src/lib/components/Popover.model.ts
Normal file
3
frontend/src/lib/components/Popover.model.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const SIDE = ['auto', 'top', 'bottom', 'left', 'right'] as const
|
||||
const ALIGN = ['start', 'end'] as const
|
||||
export type PopoverPlacement = `${typeof SIDE[number]}` | `${typeof SIDE[number]}-${typeof ALIGN[number]}`
|
||||
@@ -1,10 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { createPopperActions } from 'svelte-popperjs'
|
||||
import type { PopoverPlacement } from './Popover.model'
|
||||
|
||||
const SIDE = ['auto', 'top', 'bottom', 'left', 'right'] as const
|
||||
const ALIGN = ['start', 'end'] as const
|
||||
|
||||
export let placement: `${typeof SIDE[number]}` | `${typeof SIDE[number]}-${typeof ALIGN[number]}` = 'auto'
|
||||
export let placement: PopoverPlacement = 'auto'
|
||||
export let notClickable = false
|
||||
export let popupClass = ''
|
||||
export let disapperTimoout = 100
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
|
||||
let path = ''
|
||||
let initialPath = ''
|
||||
@@ -26,7 +27,7 @@
|
||||
let args: Record<string, any> = {}
|
||||
let can_write = true
|
||||
let loadingSchema = false
|
||||
|
||||
let linkedVars: string[] = []
|
||||
let drawer: Drawer
|
||||
|
||||
let rawCode: string | undefined = undefined
|
||||
@@ -38,6 +39,7 @@
|
||||
path = p
|
||||
resourceToEdit = undefined
|
||||
resourceSchema = undefined
|
||||
viewJsonSchema = false
|
||||
loadingSchema = true
|
||||
drawer.openDrawer?.()
|
||||
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p })
|
||||
@@ -48,6 +50,9 @@
|
||||
can_write =
|
||||
resourceToEdit.workspace_id == $workspaceStore &&
|
||||
canWrite(p, resourceToEdit.extra_perms ?? {}, $userStore)
|
||||
linkedVars = Object.entries(args)
|
||||
.filter(([_, v]) => typeof v == 'string' && v == `$var:${initialPath}`)
|
||||
.map(([k, _]) => k)
|
||||
}
|
||||
|
||||
async function editResource(): Promise<void> {
|
||||
@@ -58,7 +63,7 @@
|
||||
requestBody: { path, value: args, description }
|
||||
})
|
||||
sendUserToast(`Updated resource at ${path}`)
|
||||
dispatch('refresh')
|
||||
dispatch('refresh', path)
|
||||
drawer.closeDrawer?.()
|
||||
} else {
|
||||
throw Error('Cannot edit undefined resourceToEdit')
|
||||
@@ -100,6 +105,24 @@
|
||||
jsonError = e.message
|
||||
}
|
||||
}
|
||||
|
||||
$: linkedVars.length > 0 && path && updateArgsFromLinkedVars()
|
||||
|
||||
function updateArgsFromLinkedVars() {
|
||||
linkedVars.forEach((k) => {
|
||||
args[k] = `$var:${path}`
|
||||
})
|
||||
}
|
||||
let viewJsonSchema = false
|
||||
|
||||
function switchTab(asJson: boolean) {
|
||||
viewJsonSchema = asJson
|
||||
if (asJson) {
|
||||
rawCode = JSON.stringify(args, null, 2)
|
||||
} else {
|
||||
parseJson()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawer} size="800px">
|
||||
@@ -125,21 +148,21 @@
|
||||
kind="resource"
|
||||
/>
|
||||
</div>
|
||||
<h3>Description <Required required={false} /> </h3>
|
||||
<textarea
|
||||
type="text"
|
||||
disabled={!can_write}
|
||||
use:autosize
|
||||
bind:value={description}
|
||||
placeholder={DESCRIPTION_PLACEHOLDER}
|
||||
/>
|
||||
|
||||
<h3 class="mt-4">Value</h3>
|
||||
<div class="flex w-full justify-between max-w-lg items-center mt-4">
|
||||
<h3>Value</h3>
|
||||
<Toggle
|
||||
on:change={(e) => switchTab(e.detail)}
|
||||
options={{
|
||||
right: 'As JSON'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
{#if loadingSchema}
|
||||
<Skeleton layout={[[4]]} />
|
||||
{:else if resourceSchema && resourceSchema?.properties}
|
||||
{:else if !viewJsonSchema && resourceSchema && resourceSchema?.properties}
|
||||
<SchemaForm
|
||||
noDelete
|
||||
disabled={!can_write}
|
||||
compact
|
||||
schema={resourceSchema}
|
||||
@@ -149,10 +172,13 @@
|
||||
{:else if !can_write}
|
||||
<input type="text" disabled value={rawCode} />
|
||||
{:else}
|
||||
<p class="italic text-gray-500 text-xs mb-4"
|
||||
>No corresponding resource type found in your workspace for {selectedResourceType}.
|
||||
Define the value in JSON directly</p
|
||||
>
|
||||
{#if !viewJsonSchema}
|
||||
<p class="italic text-gray-500 text-xs mb-4"
|
||||
>No corresponding resource type found in your workspace for {selectedResourceType}.
|
||||
Define the value in JSON directly</p
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if !emptyString(jsonError)}<span
|
||||
class="text-red-400 text-xs mb-1 flex flex-row-reverse">{jsonError}</span
|
||||
>{:else}<div class="py-2" />{/if}
|
||||
@@ -167,6 +193,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<h3 class="mt-4">Description <Required required={false} /> </h3>
|
||||
<textarea
|
||||
type="text"
|
||||
disabled={!can_write}
|
||||
use:autosize
|
||||
bind:value={description}
|
||||
placeholder={DESCRIPTION_PLACEHOLDER}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
$: collection = resources.map((x) => ({
|
||||
value: x.path,
|
||||
label: `${x.path}${x.description ? ' | ' + x.description : ''}`
|
||||
label: x.path
|
||||
}))
|
||||
let appConnect: AppConnect
|
||||
let resourceEditor: ResourceEditor
|
||||
@@ -45,10 +45,20 @@
|
||||
bind:this={appConnect}
|
||||
/>
|
||||
|
||||
<ResourceEditor bind:this={resourceEditor} on:refresh={() => loadResources(resourceType)} />
|
||||
<ResourceEditor
|
||||
bind:this={resourceEditor}
|
||||
on:refresh={async (e) => {
|
||||
await loadResources(resourceType)
|
||||
console.log(e)
|
||||
if (e.detail) {
|
||||
value = e.detail
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row gap-x-1 w-full">
|
||||
<Select
|
||||
listAutoWidth={false}
|
||||
--height="34px"
|
||||
value={collection.find((x) => x.value == value)}
|
||||
bind:justValue={value}
|
||||
@@ -73,3 +83,9 @@
|
||||
}}><Icon scale={0.8} data={faRotateRight} /></Button
|
||||
>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.svelte-select-list) {
|
||||
font-size: small !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
export let autofocus = false
|
||||
export let topButton = false
|
||||
export let loading = false
|
||||
export let noVariablePicker = false
|
||||
|
||||
export let args: Record<string, any> = decodeArgs($page.url.searchParams.get('args') ?? undefined)
|
||||
|
||||
@@ -117,52 +118,55 @@
|
||||
{#if !runnable.schema.properties || Object.keys(runnable.schema.properties).length === 0}
|
||||
<div class="text-sm p-4">No arguments</div>
|
||||
{:else}
|
||||
<SchemaForm {autofocus} schema={runnable.schema} bind:isValid bind:args />
|
||||
<SchemaForm {noVariablePicker} {autofocus} schema={runnable.schema} bind:isValid bind:args />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-xs text-gray-600">No schema</div>
|
||||
{/if}
|
||||
{#if viewOptions}
|
||||
<div transition:slide class="mt-6">
|
||||
<h2>Run later</h2>
|
||||
<div class="border rounded-md p-3 pt-4">
|
||||
<div class="flex flex-row items-end">
|
||||
<div class="w-max md:w-2/3 mt-2 mb-1">
|
||||
<label for="run-time" />
|
||||
<input
|
||||
class="inline-block"
|
||||
type="datetime-local"
|
||||
id="run-time"
|
||||
name="run-scheduled-time"
|
||||
bind:value={scheduledForStr}
|
||||
min={getToday().toISOString().slice(0, 16)}
|
||||
/>
|
||||
</div>
|
||||
{#if schedulable}
|
||||
<div class="flex gap-2 items-start flex-wrap justify-between mt-2 md:mt-6 mb-6">
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
color="light"
|
||||
size="sm"
|
||||
btnClasses="mx-2 mb-1"
|
||||
on:click={() => {
|
||||
scheduledForStr = undefined
|
||||
}}
|
||||
endIcon={{ icon: viewOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewOptions = !viewOptions)}
|
||||
>
|
||||
Clear
|
||||
Schedule to run later
|
||||
</Button>
|
||||
</div>
|
||||
{#if viewOptions}
|
||||
<div transition:slide class="mt-6">
|
||||
<div class="border rounded-md p-3 pt-4">
|
||||
<div class="flex flex-row items-end">
|
||||
<div class="w-max md:w-2/3 mt-2 mb-1">
|
||||
<label for="run-time" />
|
||||
<input
|
||||
class="inline-block"
|
||||
type="datetime-local"
|
||||
id="run-time"
|
||||
name="run-scheduled-time"
|
||||
bind:value={scheduledForStr}
|
||||
min={getToday().toISOString().slice(0, 16)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
btnClasses="mx-2 mb-1"
|
||||
on:click={() => {
|
||||
scheduledForStr = undefined
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if schedulable}
|
||||
<div class="flex gap-2 items-center flex-wrap justify-between mt-2 md:mt-6 mb-6">
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
endIcon={{ icon: viewOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewOptions = !viewOptions)}
|
||||
>
|
||||
Schedule to run later
|
||||
</Button>
|
||||
{#if runnable?.path?.startsWith(`u/${$userStore?.username}`) != true && (runnable?.path?.split('/')?.length ?? 0) > 2}
|
||||
<div class="flex items-center gap-1">
|
||||
<Toggle
|
||||
@@ -183,7 +187,7 @@
|
||||
disabled={!isValid}
|
||||
on:click={() => runAction(scheduledForStr, args, invisible_to_owner)}
|
||||
>
|
||||
{scheduledForStr ? 'Schedule run to a later time' : buttonText}
|
||||
{scheduledForStr ? 'Schedule to run later' : buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
export let filter: string[] | undefined = undefined
|
||||
export let noDynamicToggle = false
|
||||
export let flexWrap = false
|
||||
export let noDelete = false
|
||||
|
||||
let clazz: string = ''
|
||||
export { clazz as class }
|
||||
@@ -43,23 +44,32 @@
|
||||
|
||||
function removeExtraKey() {
|
||||
Object.keys(args ?? {}).forEach((key) => {
|
||||
if (!Object.keys(schema?.properties ?? {}).includes(key)) {
|
||||
if (!keys.includes(key)) {
|
||||
delete args[key]
|
||||
delete inputCheck[key]
|
||||
console.log('DELETED', key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$: schema?.properties && removeExtraKey()
|
||||
|
||||
let pickForField: string | undefined
|
||||
let itemPicker: ItemPicker | undefined = undefined
|
||||
let variableEditor: VariableEditor | undefined = undefined
|
||||
let keys: string[] = []
|
||||
$: {
|
||||
let lkeys = Object.keys(schema?.properties ?? {})
|
||||
if (schema?.properties && JSON.stringify(lkeys) != JSON.stringify(keys)) {
|
||||
keys = lkeys
|
||||
if (!noDelete) {
|
||||
removeExtraKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full {clazz} {flexWrap ? 'flex flex-row flex-wrap gap-x-6 gap-y-2' : ''}">
|
||||
{#if Object.keys(schema?.properties ?? {}).length > 0}
|
||||
{#each Object.keys(schema?.properties ?? {}) as argName, i (argName)}
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName, i (argName)}
|
||||
{#if !filter || filter.includes(argName)}
|
||||
<div transition:slide|local>
|
||||
{#if inputTransform}
|
||||
@@ -149,5 +159,5 @@
|
||||
</div>
|
||||
</ItemPicker>
|
||||
|
||||
<VariableEditor bind:this={variableEditor} on:create={itemPicker.openDrawer} />
|
||||
<VariableEditor bind:this={variableEditor} />
|
||||
{/if}
|
||||
|
||||
@@ -63,6 +63,13 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
let drawer: Drawer
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key
|
||||
if (key === 'Enter') {
|
||||
dispatch('save')
|
||||
}
|
||||
}
|
||||
|
||||
export function openDrawer(): void {
|
||||
drawer.openDrawer()
|
||||
resource_type = property.format?.substring(5)
|
||||
@@ -97,6 +104,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keyup={handleKeyUp} />
|
||||
|
||||
<Drawer bind:this={drawer} placement="right">
|
||||
<DrawerContent on:close={clearModal} title="Add an argument">
|
||||
<div class="flex flex-col gap-6">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode, isInitialCode } from '$lib/script_helpers'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import { emptySchema, encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import Path from './Path.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
@@ -58,9 +58,17 @@
|
||||
try {
|
||||
$dirtyStore = false
|
||||
localStorage.removeItem(script.path)
|
||||
if (!script.schema) {
|
||||
|
||||
script.schema = script.schema ?? emptySchema()
|
||||
try {
|
||||
await inferArgs(script.language, script.content, script.schema)
|
||||
} catch (error) {
|
||||
sendUserToast(
|
||||
`Impossible to infer the schema. Assuming this is a script without main function`,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
const newHash = await ScriptService.createScript({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
@@ -84,7 +92,14 @@
|
||||
|
||||
async function changeStep(step: number) {
|
||||
if (step > 1) {
|
||||
await inferArgs(script.language, script.content, script.schema)
|
||||
script.schema = script.schema ?? emptySchema()
|
||||
try {
|
||||
await inferArgs(script.language, script.content, script.schema)
|
||||
} catch (error) {
|
||||
console.info(
|
||||
'Impossible to infer the schema. Assuming this is a script without main function'
|
||||
)
|
||||
}
|
||||
}
|
||||
goto(`?step=${step}`)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
}
|
||||
|
||||
async function inferSchema(code: string) {
|
||||
schema = schema ?? emptySchema()
|
||||
let isDefault: string[] = []
|
||||
Object.entries(args).forEach(([k, v]) => {
|
||||
if (schema.properties[k].default == v) {
|
||||
|
||||
16
frontend/src/lib/components/Slider.svelte
Normal file
16
frontend/src/lib/components/Slider.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { ChevronDown, ChevronUp } from 'lucide-svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import Button from './common/button/Button.svelte'
|
||||
|
||||
export let text: string
|
||||
export let view = false
|
||||
</script>
|
||||
|
||||
<Button color="light" on:click={() => (view = !view)} variant="border"
|
||||
>{text}
|
||||
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
|
||||
>
|
||||
{#if view}
|
||||
<div class="my-4 px-2" transition:slide><slot /></div>
|
||||
{/if}
|
||||
@@ -382,6 +382,10 @@
|
||||
export let fixedOverflowWidgets = true
|
||||
export let fontSize = 16
|
||||
|
||||
if (typeof code != 'string') {
|
||||
code = ''
|
||||
}
|
||||
|
||||
const lang = 'template'
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -589,4 +593,15 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={divEl} class="{$$props.class} rounded-lg mx-0.5" bind:clientWidth={width} />
|
||||
<div
|
||||
bind:this={divEl}
|
||||
style="height: 18px;"
|
||||
class="{$$props.class} template rounded-lg min-h-4 mx-0.5"
|
||||
bind:clientWidth={width}
|
||||
/>
|
||||
|
||||
<style>
|
||||
:global(.template .mtk20) {
|
||||
color: black !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -195,6 +195,6 @@
|
||||
}
|
||||
|
||||
onDestroy(async () => {
|
||||
await clearCurrentJob()
|
||||
currentId = undefined
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { PopoverPlacement } from './Popover.model'
|
||||
import Popover from './Popover.svelte'
|
||||
|
||||
export let light = false
|
||||
export let scale = 0.8
|
||||
export let wrapperClass = ''
|
||||
export let placement: PopoverPlacement | undefined = undefined
|
||||
</script>
|
||||
|
||||
<Popover notClickable>
|
||||
<Popover notClickable {placement} class={wrapperClass}>
|
||||
<Icon
|
||||
class="{light ? 'text-gray-300' : ' text-gray-500'} font-thin inline-block align-middle w-4"
|
||||
class="{light
|
||||
? 'text-gray-300'
|
||||
: ' text-gray-500'} font-thin inline-block align-middle w-4 {$$props.class}"
|
||||
data={faInfoCircle}
|
||||
scale={0.8}
|
||||
{scale}
|
||||
/>
|
||||
<span slot="text"> <slot /> </span>
|
||||
</Popover>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
let tokens: TruncatedToken[]
|
||||
let newToken: string | undefined
|
||||
let newTokenLabel: string | undefined
|
||||
let newTokenExpiration: string | undefined
|
||||
let newTokenExpiration: number | undefined
|
||||
let displayCreateToken = false
|
||||
let login_type = 'none'
|
||||
|
||||
@@ -57,12 +57,13 @@
|
||||
|
||||
async function createToken(): Promise<void> {
|
||||
newToken = undefined
|
||||
let expirationISO: Date | undefined
|
||||
let date: Date | undefined
|
||||
if (newTokenExpiration) {
|
||||
expirationISO = new Date(newTokenExpiration)
|
||||
date = new Date()
|
||||
date.setDate(date.getDate() + newTokenExpiration)
|
||||
}
|
||||
newToken = await UserService.createToken({
|
||||
requestBody: { label: newTokenLabel, expiration: expirationISO?.toISOString() } as NewToken
|
||||
requestBody: { label: newTokenLabel, expiration: date?.toISOString() } as NewToken
|
||||
})
|
||||
listTokens()
|
||||
displayCreateToken = false
|
||||
@@ -87,7 +88,7 @@
|
||||
Windmill {version}
|
||||
</div>
|
||||
|
||||
<h2 class="border-b">User info</h2>
|
||||
<h2 class="border-b mt-4">User info</h2>
|
||||
<div class="">
|
||||
{#if passwordError}
|
||||
<div class="text-red-600 text-2xs grow">{passwordError}</div>
|
||||
@@ -164,7 +165,7 @@
|
||||
>{truncate(newToken ?? '', 10, '****')} <Icon data={faClipboard} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="pt-1 text-xs text-right">
|
||||
<div class="pt-1 text-xs ml-2">
|
||||
Make sure to copy your personal access token now. You won’t be able to see it again!
|
||||
</div>
|
||||
</div>
|
||||
@@ -185,29 +186,17 @@
|
||||
</div>
|
||||
<div class="flex flex-col ">
|
||||
<label for="expires"
|
||||
>Expires on <span class="text-xs text-gray-500">(optional)</span>
|
||||
>Expires In<span class="text-xs text-gray-500">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
class="block md:w-1/2"
|
||||
type="date"
|
||||
id="expires"
|
||||
name="expiration-date"
|
||||
bind:value={newTokenExpiration}
|
||||
min={getToday().getFullYear() +
|
||||
'/' +
|
||||
getToday().getMonth() +
|
||||
'/' +
|
||||
getToday().getDate()}
|
||||
max={getToday().getFullYear() +
|
||||
1 +
|
||||
'/' +
|
||||
getToday().getMonth() +
|
||||
'/' +
|
||||
getToday().getDate()}
|
||||
/>
|
||||
<select bind:value={newTokenExpiration}>
|
||||
<option value={undefined}>No expiration</option>
|
||||
<option value={7}>7d</option>
|
||||
<option value={30}>30d</option>
|
||||
<option value={90}>90d</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<Button btnClasses="!mt-2" on:click={createToken}>Submit</Button>
|
||||
<Button btnClasses="!mt-2" on:click={createToken}>New Token</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
@@ -16,6 +19,6 @@
|
||||
Results
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<DisplayResult {result} />
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { InlineScript } from '../types'
|
||||
import RunnableComponent from './helpers/RunnableComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
export let inlineScript: InlineScript | undefined
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableComponent
|
||||
{id}
|
||||
bind:fields
|
||||
bind:result
|
||||
runnable={{
|
||||
name,
|
||||
inlineScript,
|
||||
type: 'runnableByName'
|
||||
}}
|
||||
wrapperClass="hidden"
|
||||
>
|
||||
<slot />
|
||||
</RunnableComponent>
|
||||
@@ -27,6 +27,7 @@
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let runnableComponent: RunnableComponent
|
||||
let disabled: boolean | undefined = undefined
|
||||
|
||||
let isLoading: boolean = false
|
||||
let ownClick: boolean = false
|
||||
@@ -50,11 +51,22 @@
|
||||
})
|
||||
|
||||
$: loading = isLoading && ownClick
|
||||
let errors: Record<string, string> = {}
|
||||
$: errorsMessage = Object.values(errors)
|
||||
.filter((x) => x != '')
|
||||
.join('\n')
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
<InputValue
|
||||
row={extraQueryParams['row']}
|
||||
{id}
|
||||
input={configuration.disabled}
|
||||
bind:value={disabled}
|
||||
bind:error={errors.disabled}
|
||||
/>
|
||||
|
||||
<RunnableWrapper
|
||||
flexWrap
|
||||
@@ -65,16 +77,20 @@
|
||||
autoRefresh={false}
|
||||
>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
{#if errorsMessage}
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
<Button
|
||||
{disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={(e) => {
|
||||
on:click={async (e) => {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
ownClick = true
|
||||
runnableComponent?.runComponent()
|
||||
await runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user