Compare commits
136 Commits
wmill-scri
...
v1.13.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e4265e18f | ||
|
|
276319d992 | ||
|
|
6dc90a3906 | ||
|
|
026a449f37 | ||
|
|
906f740a0d | ||
|
|
680aebb996 | ||
|
|
28b5671402 | ||
|
|
e127d2f79f | ||
|
|
359ef15fa2 | ||
|
|
7739c4beaa | ||
|
|
f1ee5f3130 | ||
|
|
a59b92706b | ||
|
|
9f235c404e | ||
|
|
95d98fc8fe | ||
|
|
8c4999d528 | ||
|
|
a72d6dcc40 | ||
|
|
cce46f9440 | ||
|
|
5afcb2b274 | ||
|
|
0da602d2c7 | ||
|
|
295e28fd43 | ||
|
|
c3526d3172 | ||
|
|
c3d2fd6e52 | ||
|
|
1a61d50076 | ||
|
|
f691f53224 | ||
|
|
55ec20f1de | ||
|
|
f2348b5526 | ||
|
|
75cdb228dc | ||
|
|
26b8fd159a | ||
|
|
fc8b078101 | ||
|
|
20cabe3335 | ||
|
|
0fe276b564 | ||
|
|
8a8dbcb582 | ||
|
|
587ce379d4 | ||
|
|
c8eedf7d77 | ||
|
|
ca436d1d2a | ||
|
|
9876b22d62 | ||
|
|
04093a9a14 | ||
|
|
1f6946f09b | ||
|
|
5a14d4b7d8 | ||
|
|
645e01a970 | ||
|
|
ee9d9d25bc | ||
|
|
fc19c3c247 | ||
|
|
54ca6362d6 | ||
|
|
772c5806c9 | ||
|
|
3d7a03af5f | ||
|
|
ac1cbba238 | ||
|
|
d2078f175e | ||
|
|
720093962a | ||
|
|
e471a1d646 | ||
|
|
9e6ab11484 | ||
|
|
06eb50fbf2 | ||
|
|
40a380e9ec | ||
|
|
c638f7a132 | ||
|
|
d2f4a552c9 | ||
|
|
479a12f33c | ||
|
|
58e2a5c179 | ||
|
|
281fbc3671 | ||
|
|
ffc58ab6c2 | ||
|
|
0ea96f82d1 | ||
|
|
e905d65ca6 | ||
|
|
dc70dfcf74 | ||
|
|
9b79cc9870 | ||
|
|
68a3e1b333 | ||
|
|
917717373f | ||
|
|
b61fb6dc30 | ||
|
|
42aa386119 | ||
|
|
d601ef9439 | ||
|
|
d31cd3c52c | ||
|
|
eb613c35c1 | ||
|
|
33fed8e04d | ||
|
|
37afd486fd | ||
|
|
f76eede3b0 | ||
|
|
7564d2cb1e | ||
|
|
f12fe85fef | ||
|
|
fd9285563a | ||
|
|
605c2b4d11 | ||
|
|
18b4ab2e73 | ||
|
|
02fb2b3806 | ||
|
|
563ba3e7f7 | ||
|
|
3eed59fcb1 | ||
|
|
7365a8e87b | ||
|
|
dbd6142997 | ||
|
|
865d728224 | ||
|
|
8861e19564 | ||
|
|
92b502d9ba | ||
|
|
297a3e60e2 | ||
|
|
1decaafde0 | ||
|
|
a7ef616c0d | ||
|
|
481685a73e | ||
|
|
a356e7b7d3 | ||
|
|
f793bc46d9 | ||
|
|
c49e4930bc | ||
|
|
7b6ae612a5 | ||
|
|
d179d6efc3 | ||
|
|
f02e5b19ac | ||
|
|
e114d0f426 | ||
|
|
03ec38e001 | ||
|
|
2e1d43033f | ||
|
|
ec528fce67 | ||
|
|
5b413d7e04 | ||
|
|
02c8bea084 | ||
|
|
bb31c80378 | ||
|
|
91045e73cc | ||
|
|
9219b651a3 | ||
|
|
7f21d03d00 | ||
|
|
a62e6e5ee3 | ||
|
|
2c28031e44 | ||
|
|
ca8de69126 | ||
|
|
98071bd68b | ||
|
|
128dde4fb3 | ||
|
|
f090945b27 | ||
|
|
60729d80b9 | ||
|
|
e228beec2a | ||
|
|
4dbf562fb7 | ||
|
|
4952290296 | ||
|
|
f53eb71e4a | ||
|
|
96f54f5f44 | ||
|
|
0863e12e6a | ||
|
|
d03266b0a4 | ||
|
|
4a4eaa90e2 | ||
|
|
5e7c14b722 | ||
|
|
55b5695673 | ||
|
|
8596ac50b9 | ||
|
|
13fb52117b | ||
|
|
2c70a15594 | ||
|
|
7a51f842f0 | ||
|
|
a130806e19 | ||
|
|
fd1f05dd16 | ||
|
|
48e51733e0 | ||
|
|
e7817e6c9f | ||
|
|
51ad6edfcb | ||
|
|
315f7edd64 | ||
|
|
a2c3deab74 | ||
|
|
891b7eb93a | ||
|
|
7efd87be79 | ||
|
|
5acbc8b48c |
6
.env
6
.env
@@ -1,3 +1,5 @@
|
||||
SITE_URL=localhost
|
||||
DB_PASSWORD=changeme
|
||||
POSTGRES_VERSION=13.3.0
|
||||
|
||||
# GitHub OAuth- https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app
|
||||
GITHUB_OAUTH_CLIENT_ID=yours_client_id
|
||||
GITHUB_OAUTH_CLIENT_SECRET=yours_client_sected
|
||||
|
||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @rubenfiszel
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rubenfiszel]
|
||||
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
title: 'bug:'
|
||||
labels: 'bug'
|
||||
assignees: 'rubenfiszel'
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
**Describe the bug** A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce** Steps to reproduce the behavior:
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
**Expected behavior** A clear and concise description of what you expected to
|
||||
happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
**Screenshots** If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
**Windmill version** Go on the left menu -> <user> -> User Settings and copy the
|
||||
printed version in "Running windmill version (backend): XXX".
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
**Additional context** Add any other context about the problem here.
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Create a feature request
|
||||
title: 'feature: '
|
||||
labels: 'feature'
|
||||
assignees: 'rubenfiszel'
|
||||
|
||||
---
|
||||
8
.github/change-versions.sh
vendored
8
.github/change-versions.sh
vendored
@@ -9,8 +9,10 @@ sed -i -e "/\"version\": /s/: .*,/: \"$VERSION\",/" frontend/package.json
|
||||
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" python-client/wmill/pyproject.toml
|
||||
sed -i -e "/^windmill-api =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill/pyproject.toml
|
||||
sed -i -e "/^version =/s/= .*/= \"$VERSION\"/" python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" Pipfile
|
||||
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" Pipfile
|
||||
# sed -i -e "/^wmill =/s/= .*/= \"\\^$VERSION\"/" python-client/wmill_pg/pyproject.toml
|
||||
sed -i -e "/^wmill =/s/= .*/= \">=$VERSION\"/" lsp/Pipfile
|
||||
sed -i -e "/^wmill_pg =/s/= .*/= \">=$VERSION\"/" lsp/Pipfile
|
||||
|
||||
sed -i -zE "s/name = \"windmill\"\nversion = \"[^\"]*\"\\n(.*)/name = \"windmill\"\nversion = \"$VERSION\"\\n\\1/" backend/Cargo.lock
|
||||
|
||||
cd frontend && npm i --package-lock-only
|
||||
|
||||
39
.github/dependabot.yml
vendored
Normal file
39
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# Basic set up for three package managers
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/frontend"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for cargo
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/backend"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for Docker
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for wmill python client
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/python-client/wmill"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
# Maintain dependencies for wmill_pg python client
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/python-client/wmill_pg"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
3
.github/workflows/change-versions.yml
vendored
3
.github/workflows/change-versions.yml
vendored
@@ -7,8 +7,9 @@ on:
|
||||
jobs:
|
||||
change_version:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:18
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Change versions
|
||||
run: ./.github/change-versions.sh "$(cat version.txt)"
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
|
||||
49
.github/workflows/deno_on_release.yml
vendored
Normal file
49
.github/workflows/deno_on_release.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Publish deno-client
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
env:
|
||||
repo: windmill-deno-client
|
||||
|
||||
jobs:
|
||||
build_deno_and_push_to_repo:
|
||||
runs-on: ubuntu-latest
|
||||
container: openapitools/openapi-generator-cli:v6.0.0-beta
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: generate_deno
|
||||
run: |
|
||||
cd deno-client
|
||||
rm .gitignore
|
||||
./generate.sh
|
||||
- name: Pushes to another repository
|
||||
id: push_directory
|
||||
uses: cpina/github-action-push-to-another-repository@devel
|
||||
env:
|
||||
API_TOKEN_GITHUB: ${{ secrets.DENO_PAT }}
|
||||
with:
|
||||
source-directory: deno-client/
|
||||
destination-github-username: ${{ github.repository_owner }}
|
||||
destination-repository-name: ${{ env.repo }}
|
||||
user-email: ruben@windmill.dev
|
||||
commit-message: See ORIGIN_COMMIT from $GITHUB_REF
|
||||
target-branch: main
|
||||
|
||||
tag_repo:
|
||||
needs: [build_deno_and_push_to_repo]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ${{ github.repository_owner }}/${{ env.repo }}
|
||||
token: ${{ secrets.DENO_PAT }}
|
||||
path: ./client
|
||||
|
||||
- name: Push client
|
||||
run: |
|
||||
cd ./client
|
||||
git config --global user.email "ruben@windmill.dev"
|
||||
git config --global user.name "rubenfiszel[bot]"
|
||||
git tag -a ${{ github.ref_name }} -m "${{ github.ref_name }}"
|
||||
git push --tags
|
||||
2
.github/workflows/deploy_to_windmill.yml
vendored
2
.github/workflows/deploy_to_windmill.yml
vendored
@@ -3,6 +3,8 @@ name: Deploy to windmill.dev
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "community/*"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
121
.github/workflows/docker-image.yml
vendored
121
.github/workflows/docker-image.yml
vendored
@@ -1,7 +1,13 @@
|
||||
name: Docker Image CI
|
||||
env:
|
||||
LOCAL_REGISTRY: registry.wimill.xyz
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
name: Build and push docker image
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["*"]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
@@ -12,28 +18,97 @@ concurrency:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, new]
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
steps:
|
||||
- name: Wait for release to succeed
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: lewagon/wait-on-check-action@v1.0.0
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
check-name: "Release please"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: deploy staging stack
|
||||
run: |
|
||||
docker build . --cache-from "registry.wimill.xyz/windmill:staging" -t "registry.wimill.xyz/windmill:staging" --build-arg BUILDKIT_INLINE_CACHE=1
|
||||
docker push "registry.wimill.xyz/windmill:staging"
|
||||
- name: deploy demo stack
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
docker tag registry.wimill.xyz/windmill:staging registry.wimill.xyz/windmill:main
|
||||
docker push registry.wimill.xyz/windmill:main
|
||||
# - name: pruning unused images
|
||||
# run: sudo docker image prune -a
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Docker meta local
|
||||
id: metalocal
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push privately
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ steps.metalocal.outputs.tags }}
|
||||
|
||||
labels: ${{ steps.metalocal.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
|
||||
|
||||
- name: Docker meta
|
||||
if: github.event_name != 'pull_request'
|
||||
id: meta
|
||||
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
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publically
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ steps.metalocal.outputs.tags }}
|
||||
${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.metalocal.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
|
||||
cypress:
|
||||
runs-on: [self-hosted, new]
|
||||
needs: [build]
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_DB: windmill
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: changeme
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Docker"
|
||||
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
|
||||
id: docker-container
|
||||
- name: "Cypress run"
|
||||
uses: cypress-io/github-action@v4
|
||||
with:
|
||||
working-directory: ./frontend
|
||||
config: baseUrl=http://localhost:8000
|
||||
- name: "Clean up"
|
||||
run: docker kill ${{ steps.docker-container.outputs.id }}
|
||||
if: always()
|
||||
|
||||
41
.github/workflows/lsp_on_release.yml
vendored
Normal file
41
.github/workflows/lsp_on_release.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
env:
|
||||
LOCAL_REGISTRY: registry.wimill.xyz
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
name: Publish LSP Server
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "python-client/*W"
|
||||
- "lsp/*"
|
||||
tags:
|
||||
- "*"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [self-hosted, new]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Docker meta local
|
||||
id: metalocal
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-lsp
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: "{{defaultContext}}:lsp"
|
||||
push: true
|
||||
tags: ${{ steps.metalocal.outputs.tags }}
|
||||
labels: ${{ steps.metalocal.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-lsp:buildcache
|
||||
cache-to: type=registry,ref=${{ env.LOCAL_REGISTRY }}/${{ env.IMAGE_NAME }}-lsp:buildcache,mode=max
|
||||
38
.github/workflows/on-release.yml
vendored
38
.github/workflows/on-release.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Build LSP Docker
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "python-client/**"
|
||||
- "Pipfile"
|
||||
- ".github/workflows/on-release.yml"
|
||||
|
||||
jobs:
|
||||
build_lsp:
|
||||
runs-on: [self-hosted, new]
|
||||
steps:
|
||||
- name: Wait for release to succeed
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: lewagon/wait-on-check-action@v1.0.0
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
check-name: "Release please"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
- uses: actions/checkout@v2
|
||||
- name: Upload python client
|
||||
env:
|
||||
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
cd python-client
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
export PATH=$PATH:/root/.local/bin
|
||||
./publish.sh
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
cd lsp
|
||||
sudo docker pull "registry.wimill.xyz/lsp:main" || true
|
||||
sudo docker build . --cache-from "registry.wimill.xyz/lsp:main" -t "registry.wimill.xyz/lsp:main" --build-arg BUILDKIT_INLINE_CACHE=1
|
||||
- name: push to registry
|
||||
run: |
|
||||
sudo docker push "registry.wimill.xyz/lsp:main"
|
||||
19
.github/workflows/pypi_on_release.yml
vendored
Normal file
19
.github/workflows/pypi_on_release.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Publish python-client
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
publish_pypi:
|
||||
runs-on: [self-hosted, new]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Upload python client
|
||||
env:
|
||||
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
cd python-client
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
export PATH=$PATH:/root/.local/bin
|
||||
./publish.sh
|
||||
6
.github/workflows/release-please.yml
vendored
6
.github/workflows/release-please.yml
vendored
@@ -1,14 +1,14 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [main]
|
||||
|
||||
name: release-please
|
||||
jobs:
|
||||
release-please:
|
||||
name: "Release please"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: GoogleCloudPlatform/release-please-action@v2
|
||||
- uses: GoogleCloudPlatform/release-please-action@v3
|
||||
with:
|
||||
release-type: simple
|
||||
package-name: windmill
|
||||
|
||||
34
.github/workflows/sign-cla.yml
vendored
Normal file
34
.github/workflows/sign-cla.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: "CLA Assistant"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened, closed, synchronize]
|
||||
|
||||
jobs:
|
||||
CLAssistant:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||
# Beta Release
|
||||
uses: cla-assistant/github-action@v2.1.3-beta
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }}
|
||||
with:
|
||||
path-to-signatures: "signatures/cla.json"
|
||||
path-to-document: "https://github.com/windmill-labs/windmill/blob/master/CLA.md"
|
||||
branch: "signatures"
|
||||
allowlist: rubenfiszel,bot*
|
||||
|
||||
#below are the optional inputs - If the optional inputs are not given, then default values will be taken
|
||||
#remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
|
||||
#remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository)
|
||||
#create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
|
||||
#signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo'
|
||||
#custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign'
|
||||
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
|
||||
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
|
||||
#lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
|
||||
#use-dco-flag: true - If you are using DCO instead of CLA
|
||||
144
CHANGELOG.md
144
CHANGELOG.md
@@ -1,3 +1,147 @@
|
||||
# Changelog
|
||||
|
||||
## [1.13.0](https://github.com/windmill-labs/windmill/compare/v1.12.0...v1.13.0) (2022-06-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* better type narrowing for list and array types ([276319d](https://github.com/windmill-labs/windmill/commit/276319d99240dbca5bcc74a1142d99ca823c4da2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix webhook path for flows ([906f740](https://github.com/windmill-labs/windmill/commit/906f740a0ddce26743e4669af7a101613131a17c))
|
||||
* make email constraint case insensitive ([6dc90a3](https://github.com/windmill-labs/windmill/commit/6dc90a390643fcf6116289596ca1c3149d326797))
|
||||
|
||||
## [1.12.0](https://github.com/windmill-labs/windmill/compare/v1.11.0...v1.12.0) (2022-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* more flexible ResourceType MainArgSignature parser ([359ef15](https://github.com/windmill-labs/windmill/commit/359ef15fa2a9024507a71f2c656373925fba3ebe))
|
||||
* rename ResourceType -> Resource ([28b5671](https://github.com/windmill-labs/windmill/commit/28b56714023ea69a20f003e08f6c40de64202ac5))
|
||||
|
||||
## [1.11.0](https://github.com/windmill-labs/windmill/compare/v1.10.1...v1.11.0) (2022-06-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add DISABLE_NUSER for older kernels ([cce46f9](https://github.com/windmill-labs/windmill/commit/cce46f94404ac5c10407e430fff8cdec3bd7fb2d))
|
||||
* add ResourceType<'name'> as deno signature arg type ([f1ee5f3](https://github.com/windmill-labs/windmill/commit/f1ee5f3130cb7b753ccc3ee62169c5e4a8ef7b8b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* force c_ prefix for adding resource type ([9f235c4](https://github.com/windmill-labs/windmill/commit/9f235c404ed62b54a73451b9f9dbddd8f013120d))
|
||||
* **frontend:** loadItems not called in script picker ([a59b927](https://github.com/windmill-labs/windmill/commit/a59b92706b24a07cc14288620a9bcdb9402bd134))
|
||||
|
||||
## [1.10.1](https://github.com/windmill-labs/windmill/compare/v1.10.0...v1.10.1) (2022-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* python-client verify ssl ([295e28f](https://github.com/windmill-labs/windmill/commit/295e28fd43ef07b739d2c7c85b0ae6819f7d7434))
|
||||
|
||||
## [1.10.0](https://github.com/windmill-labs/windmill/compare/v1.9.0...v1.10.0) (2022-06-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* alpha hub integration + frontend user store fixes + script client base_url fix ([1a61d50](https://github.com/windmill-labs/windmill/commit/1a61d50076b295fe97e48c2a621dff30802152b1))
|
||||
|
||||
## [1.9.0](https://github.com/windmill-labs/windmill/compare/v1.8.6...v1.9.0) (2022-06-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update postgres 13->14 in docker-compose ([479a12f](https://github.com/windmill-labs/windmill/commit/479a12f33ca26bfd1b67bcdd24a64ca26cc6bebe))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove annoying transitions for scripts and flows ([f2348b5](https://github.com/windmill-labs/windmill/commit/f2348b5526bb8197519685cb57049f74c6f3a11d))
|
||||
|
||||
### [1.8.6](https://github.com/windmill-labs/windmill/compare/v1.8.5...v1.8.6) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* re-release ([d31cd3c](https://github.com/windmill-labs/windmill/commit/d31cd3c52c1b46e821da261f22d0aec872b61fb2))
|
||||
|
||||
### [1.8.5](https://github.com/windmill-labs/windmill/compare/v1.8.4...v1.8.5) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* language field broke flow too ([33fed8e](https://github.com/windmill-labs/windmill/commit/33fed8e04d3abbde371535ecb6e7ba15d103db92))
|
||||
|
||||
### [1.8.4](https://github.com/windmill-labs/windmill/compare/v1.8.3...v1.8.4) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* scripts run was broken due to 1.7 and 1.8 changes. This fix it ([7564d2c](https://github.com/windmill-labs/windmill/commit/7564d2cb1e7f600ede22f333a02a537df381d829))
|
||||
|
||||
### [1.8.3](https://github.com/windmill-labs/windmill/compare/v1.8.2...v1.8.3) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clean exported deno-client api ([605c2b4](https://github.com/windmill-labs/windmill/commit/605c2b4d11bf072332a38f0c3e24cf6cc9ec7e65))
|
||||
|
||||
### [1.8.2](https://github.com/windmill-labs/windmill/compare/v1.8.1...v1.8.2) (2022-05-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* deno client ([563ba3e](https://github.com/windmill-labs/windmill/commit/563ba3e7f763279a93f619933ac35a1dec3f727a))
|
||||
* deno lsp client ([3eed59f](https://github.com/windmill-labs/windmill/commit/3eed59fcb1b172ab13f65c9a0caa0545f5ed91da))
|
||||
* deno lsp uses wss instead of ws ([865d728](https://github.com/windmill-labs/windmill/commit/865d728224bed55fe4a2c1905ff2b8c15f4bbe17))
|
||||
* starting deno script is now async ([7365a8e](https://github.com/windmill-labs/windmill/commit/7365a8e87bdb1f879eb92125a9e6378a1636637e))
|
||||
|
||||
### [1.8.1](https://github.com/windmill-labs/windmill/compare/v1.8.0...v1.8.1) (2022-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* frontend dependencies update ([f793bc4](https://github.com/windmill-labs/windmill/commit/f793bc46d98349a5fea56c7911b6e0720b2b117c))
|
||||
|
||||
## [1.8.0](https://github.com/windmill-labs/windmill/compare/v1.7.0...v1.8.0) (2022-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Typescript support for scripts (alpha) ([2e1d430](https://github.com/windmill-labs/windmill/commit/2e1d43033f3ad6dbe86338b7a41da7b1120a5ffc))
|
||||
|
||||
## [1.7.0](https://github.com/windmill-labs/windmill/compare/v1.6.1...v1.7.0) (2022-05-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* self host github oauth ([#46](https://github.com/windmill-labs/windmill/issues/46)) ([5b413d7](https://github.com/windmill-labs/windmill/commit/5b413d7e045d09dc5c5916cb22d82438ec6c92ad))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* better error message when saving script ([02c8bea](https://github.com/windmill-labs/windmill/commit/02c8bea0840e492c31ccb8ddd1e5ae9676a534b1))
|
||||
|
||||
### [1.6.1](https://github.com/windmill-labs/windmill/compare/v1.6.0...v1.6.1) (2022-05-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* also store and display "started at" for completed jobs ([#33](https://github.com/windmill-labs/windmill/issues/33)) ([2c28031](https://github.com/windmill-labs/windmill/commit/2c28031e44453740ad8c4b7e3c248173eab34b9c))
|
||||
|
||||
## 1.6.0 (2022-05-10)
|
||||
|
||||
### Features
|
||||
|
||||
* superadmin settings ([7a51f84](https://www.github.com/windmill-labs/windmill/commit/7a51f842f01e17c4d230c060fa0de558553ad3ed))
|
||||
* user settings is now at workspace level ([a130806](https://www.github.com/windmill-labs/windmill/commit/a130806e1929267ee40ca443e3dac6e1a5d80da3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* display more than default 30 workspaces as superadmin ([55b5695](https://www.github.com/windmill-labs/windmill/commit/55b5695673912ffe040d3011c020b1002b4e3268))
|
||||
|
||||
## [1.5.0](https://www.github.com/windmill-labs/windmill/v1.5.0) (2022-05-02)
|
||||
|
||||
145
CLA.md
Normal file
145
CLA.md
Normal file
@@ -0,0 +1,145 @@
|
||||
## Contributor Agreement
|
||||
|
||||
## Individual Contributor Non-Exclusive License Agreement
|
||||
|
||||
Thank you for your interest in contributing to Ruben Fiszel's Windmill ("We" or
|
||||
"Us").
|
||||
|
||||
The purpose of this contributor agreement ("Agreement") is to clarify and
|
||||
document the rights granted by contributors to Us.
|
||||
|
||||
### 1\. Definitions
|
||||
|
||||
**"You"** means the individual Copyright owner who Submits a Contribution to Us.
|
||||
|
||||
**"Legal Entity"** means an entity that is not a natural person.
|
||||
|
||||
**"Affiliate"** means any other Legal Entity that controls, is controlled by, or
|
||||
under common control with that Legal Entity. For the purposes of this
|
||||
definition, "control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such Legal Entity, whether by contract or otherwise,
|
||||
(ii) ownership of fifty percent (50%) or more of the outstanding shares or
|
||||
securities that vote to elect the management or other persons who direct such
|
||||
Legal Entity or (iii) beneficial ownership of such entity.
|
||||
|
||||
**"Contribution"** means any original work of authorship, including any original
|
||||
modifications or additions to an existing work of authorship, Submitted by You
|
||||
to Us, in which You own the Copyright.
|
||||
|
||||
**"Copyright"** means all rights protecting works of authorship, including
|
||||
copyright, moral and neighboring rights, as appropriate, for the full term of
|
||||
their existence.
|
||||
|
||||
**"Material"** means the software or documentation made available by Us to third
|
||||
parties.
|
||||
|
||||
**"Submit"** means any act by which a Contribution is transferred to Us by You
|
||||
by means of tangible or intangible media, including but not limited to
|
||||
electronic mailing lists, source code control systems, and issue tracking
|
||||
systems that are managed by, or on behalf of, Us, but excluding any transfer
|
||||
that is conspicuously marked or otherwise designated in writing by You as "Not a
|
||||
Contribution."
|
||||
|
||||
**"Documentation"** means any non-software portion of a Contribution.
|
||||
|
||||
### 2\. License grant
|
||||
|
||||
#### 2.1 Copyright license to Us
|
||||
|
||||
Subject to the terms and conditions of this Agreement, You hereby grant to Us a
|
||||
worldwide, royalty-free, NON-exclusive, perpetual and irrevocable (except as
|
||||
stated in Section 8.2) license, with the right to transfer an unlimited number
|
||||
of non-exclusive licenses or to grant sublicenses to third parties, under the
|
||||
Copyright covering the Contribution to use the Contribution by all means,
|
||||
including, but not limited to:
|
||||
|
||||
- publish the Contribution,
|
||||
- modify the Contribution,
|
||||
- prepare derivative works based upon or containing the Contribution and/or to
|
||||
combine the Contribution with other Materials,
|
||||
- reproduce the Contribution in original or modified form,
|
||||
- distribute, to make the Contribution available to the public, display and
|
||||
publicly perform the Contribution in original or modified form.
|
||||
|
||||
#### 2.2 Moral rights
|
||||
|
||||
Moral Rights remain unaffected to the extent they are recognized and not
|
||||
waivable by applicable law. Notwithstanding, You may add your name to the
|
||||
attribution mechanism customary used in the Materials you Contribute to, such as
|
||||
the header of the source code files of Your Contribution, and We will respect
|
||||
this attribution when using Your Contribution.
|
||||
|
||||
### 3\. Patents
|
||||
|
||||
#### 3.1 Patent license
|
||||
|
||||
Subject to the terms and conditions of this Agreement You hereby grant to Us and
|
||||
to recipients of Materials distributed by Us a worldwide, royalty-free,
|
||||
non-exclusive, perpetual and irrevocable (except as stated in Section 3.2)
|
||||
patent license, with the right to transfer an unlimited number of non-exclusive
|
||||
licenses or to grant sublicenses to third parties, to make, have made, use,
|
||||
sell, offer for sale, import and otherwise transfer the Contribution and the
|
||||
Contribution in combination with any Material (and portions of such
|
||||
combination). This license applies to all patents owned or controlled by You,
|
||||
whether already acquired or hereafter acquired, that would be infringed by
|
||||
making, having made, using, selling, offering for sale, importing or otherwise
|
||||
transferring of Your Contribution(s) alone or by combination of Your
|
||||
Contribution(s) with any Material.
|
||||
|
||||
### 4. Disclaimer
|
||||
|
||||
THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED
|
||||
WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF SATISFACTORY
|
||||
QUALITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY
|
||||
DISCLAIMED BY YOU TO US AND BY US TO YOU. TO THE EXTENT THAT ANY SUCH WARRANTIES
|
||||
CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION AND EXTENT TO THE
|
||||
MINIMUM PERIOD AND EXTENT PERMITTED BY LAW.
|
||||
|
||||
### 5. Consequential damage waiver
|
||||
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU OR WE BE
|
||||
LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA,
|
||||
INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT
|
||||
OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR
|
||||
OTHERWISE) UPON WHICH THE CLAIM IS BASED.
|
||||
|
||||
### 6. Approximation of disclaimer and damage waiver
|
||||
|
||||
IF THE DISCLAIMER AND DAMAGE WAIVER MENTIONED IN SECTION 4. AND SECTION 5.
|
||||
CANNOT BE GIVEN LEGAL EFFECT UNDER APPLICABLE LOCAL LAW, REVIEWING COURTS SHALL
|
||||
APPLY LOCAL LAW THAT MOST CLOSELY APPROXIMATES AN ABSOLUTE WAIVER OF ALL CIVIL
|
||||
OR CONTRACTUAL LIABILITY IN CONNECTION WITH THE CONTRIBUTION.
|
||||
|
||||
### 7. Term
|
||||
|
||||
7.1 This Agreement shall come into effect upon Your acceptance of the terms and
|
||||
conditions.
|
||||
|
||||
7.3 In the event of a termination of this Agreement Sections 4, 5, 6, 7 and 8
|
||||
shall survive such termination and shall remain in full force thereafter. For
|
||||
the avoidance of doubt, Free and Open Source Software (sub)licenses that have
|
||||
already been granted for Contributions at the date of the termination shall
|
||||
remain in full force after the termination of this Agreement.
|
||||
|
||||
### 8 Miscellaneous
|
||||
|
||||
8.1 This Agreement and all disputes, claims, actions, suits or other proceedings
|
||||
arising out of this agreement or relating in any way to it shall be governed by
|
||||
the laws of France excluding its private international law provisions.
|
||||
|
||||
8.2 This Agreement sets out the entire agreement between You and Us for Your
|
||||
Contributions to Us and overrides all other agreements or understandings.
|
||||
|
||||
8.3 In case of Your death, this agreement shall continue with Your heirs. In
|
||||
case of more than one heir, all heirs must exercise their rights through a
|
||||
commonly authorized person.
|
||||
|
||||
8.4 If any provision of this Agreement is found void and unenforceable, such
|
||||
provision will be replaced to the extent possible with a provision that comes
|
||||
closest to the meaning of the original provision and that is enforceable. The
|
||||
terms and conditions set forth in this Agreement shall apply notwithstanding any
|
||||
failure of essential purpose of this Agreement or any limited remedy to the
|
||||
maximum extent possible under law.
|
||||
|
||||
8.5 You agree to notify Us of any facts or circumstances of which you become
|
||||
aware that would make this Agreement inaccurate in any respect.
|
||||
@@ -19,7 +19,7 @@ RUN git clone -b master --single-branch https://github.com/google/nsjail.git . \
|
||||
&& git checkout dccf911fd2659e7b08ce9507c25b2b38ec2c5800
|
||||
RUN make
|
||||
|
||||
FROM mhart/alpine-node:14 as frontend
|
||||
FROM mhart/alpine-node:16 as frontend
|
||||
|
||||
# install dependencies
|
||||
WORKDIR /frontend
|
||||
@@ -90,6 +90,8 @@ 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 mkdir -p ${APP}
|
||||
|
||||
WORKDIR ${APP}
|
||||
|
||||
64
README.md
64
README.md
@@ -1,10 +1,13 @@
|
||||
<p align="center">
|
||||
<a href="https://alpha.windmill.dev"><img src="./windmill.svg" alt="windmill.dev"></a>
|
||||
<a href="https://app.windmill.dev"><img src="./imgs/windmill.svg" alt="windmill.dev"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>Windmill.dev is an OSS developer platform to quickly build production-grade multi-steps automations and internal apps from minimal Python and Typescript scripts.</em>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/windmill-labs/windmill/actions/workflows/docker-image.yml" target="_blank">
|
||||
<img src="https://github.com/windmill-labs/windmill/actions/workflows/docker-image.yml/badge.svg" alt="Docker Image CI">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/wmill" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/wmill?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
@@ -16,7 +19,7 @@
|
||||
---
|
||||
|
||||
**Join the alpha (personal workspaces are free forever)**:
|
||||
<https://alpha.windmill.dev>
|
||||
<https://app.windmill.dev>
|
||||
|
||||
**Documentation**: <https://docs.windmill.dev>
|
||||
|
||||
@@ -36,17 +39,36 @@ You can show your support for the project by starring this repo.
|
||||
especially concerning flows.
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
Windmill is <b>fully open-sourced</b>:
|
||||
|
||||
- `community/` and `python-client/` are Apache 2.0
|
||||
- `community/`, `python-client/` and `deno-client/` are Apache 2.0
|
||||
- backend, frontend and everything else under AGPLv3.
|
||||
|
||||
## What is the general idea behind Windmill
|
||||
|
||||
1. Define a minimal and generic script in Python or Typescript that solve a
|
||||
specific task. Here sending an email with SMTP. The code can be defined in
|
||||
the provided Web IDE or synchronized with your own github repo:
|
||||

|
||||
|
||||
2. Your scripts parameters are automatically parsed and generate a frontend. You
|
||||
can narrow down the types during task definition to specify regex for string,
|
||||
an enum or a specific format for objects. Each script correspond to an app by
|
||||
itself: 
|
||||
|
||||
3. Make it flow! You can chain your scripts or scripts made by the community
|
||||
inside flow by piping output to input using "Dynamic" fields that are just
|
||||
plain Javascript. You can also refer to external variables, output from any
|
||||
steps or inputs of the flow itself. The flow parameters then generate
|
||||
automatically an intuitive forms that can be triggered by anyone, like for
|
||||
scripts. 
|
||||
|
||||
## Layout
|
||||
|
||||
- `backend/`: The whole Rust backend
|
||||
- `frontend`: The whole Svelte fronten
|
||||
- `frontend`: The whole Svelte frontend
|
||||
- `community/`: Scripts and resource types created and curated by the community,
|
||||
included in every workspace
|
||||
- `lsp/`: The lsp asssistant for the monaco editor
|
||||
@@ -54,11 +76,13 @@ Windmill is <b>fully open-sourced</b>:
|
||||
execution
|
||||
- `python-client/`: The wmill python client used within scripts to interact with
|
||||
the windmill platform
|
||||
- `deno-client/`: The wmill deno client used within scripts to interact with the
|
||||
windmill platform
|
||||
|
||||
## Stack
|
||||
|
||||
- postgres as the database
|
||||
- backend in Rust with the follwing highly-available and horizontally scalable
|
||||
- backend in Rust with the following highly-available and horizontally scalable
|
||||
architecture:
|
||||
- stateless API backend
|
||||
- workers that pull jobs from a queue
|
||||
@@ -69,18 +93,36 @@ Windmill is <b>fully open-sourced</b>:
|
||||
- typescript runtime is deno
|
||||
- python runtime is python3
|
||||
|
||||
## Architecture
|
||||
|
||||
A detailed section about Windmill architecture is coming soon
|
||||
|
||||
### Development stack
|
||||
|
||||
- caddy is the reverse proxy used for local development, see frontend's
|
||||
Caddyfile and CaddyfileRemote
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
## How to self-host
|
||||
|
||||
Complete instructions coming soon
|
||||
`docker compose up` with the following docker-compose is sufficient:
|
||||
<https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml>
|
||||
|
||||
For older kernels < 4.18, set DISABLE_NUSER to true otherwise nsjail will not be
|
||||
able to launch the isolated scripts.
|
||||
|
||||
The default super-admin user is: admin@windmill.dev / changeme
|
||||
|
||||
From there, you can create other users (do not forget to change the password!)
|
||||
|
||||
Detailed instructions for more complex deployments will come soon. For simpler
|
||||
docker based ones, the docker-compose.yml file contains all the necessary
|
||||
informations.
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/windmill-labs/windmill/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=windmill-labs/windmill" />
|
||||
</a>
|
||||
|
||||
## Copyright
|
||||
|
||||
|
||||
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
target/
|
||||
.env
|
||||
v8.snap
|
||||
|
||||
1012
backend/Cargo.lock
generated
1012
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.5.0"
|
||||
version = "1.13.0"
|
||||
authors = ["Ruben Fiszel <ruben@rubenfiszel.com>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -55,6 +55,9 @@ regex = "^1"
|
||||
deno_core = "^0"
|
||||
indexmap = "~1.6.2"
|
||||
async-recursion = "^1"
|
||||
swc_common = "^0"
|
||||
swc_ecma_parser = "^0"
|
||||
swc_ecma_ast = "^0"
|
||||
|
||||
sqlx = { version = "^0", features = ["macros", "offline", "migrate", "uuid", "json", "chrono", "postgres", "runtime-tokio-rustls"]}
|
||||
dotenv = "^0"
|
||||
|
||||
@@ -5,6 +5,8 @@ use deno_core::{JsRuntime, RuntimeOptions};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=Cargo.lock");
|
||||
|
||||
let options = RuntimeOptions {
|
||||
will_snapshot: true,
|
||||
..Default::default()
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
-- Add down migration script here
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Add down migration script here
|
||||
DROP TYPE SCRIPT_LANG;
|
||||
|
||||
ALTER TABLE script
|
||||
DROP COLUMN language SCRIPT_LANG;
|
||||
|
||||
ALTER TABLE queue
|
||||
DROP COLUMN language SCRIPT_LANG;
|
||||
|
||||
ALTER TABLE completed_job
|
||||
DROP COLUMN language SCRIPT_LANG;
|
||||
|
||||
11
backend/migrations/20220504193929_typescript_support.up.sql
Normal file
11
backend/migrations/20220504193929_typescript_support.up.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Add up migration script here
|
||||
CREATE TYPE SCRIPT_LANG AS ENUM ('python3', 'deno');
|
||||
|
||||
ALTER TABLE script
|
||||
ADD COLUMN language SCRIPT_LANG NOT NULL DEFAULT 'python3';
|
||||
|
||||
ALTER TABLE queue
|
||||
ADD COLUMN language SCRIPT_LANG NOT NULL DEFAULT 'python3';
|
||||
|
||||
ALTER TABLE completed_job
|
||||
ADD COLUMN language SCRIPT_LANG NOT NULL DEFAULT 'python3';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
UPDATE password
|
||||
SET password_hash = '$argon2id$v=19$m=4096,t=3,p=1$oLJo/lPn/gezXCuFOEyaNw$i0T2tCkw3xUFsrBIKZwr8jVNHlIfoxQe+HfDnLtd12I'
|
||||
WHERE password_hash = '$argon2id$v=19$m=4096,t=3,p=1$z0Kg3qyaS14e+YHeihkJLQ$N69flI6yQ/U98pjAHtbNxbdz2f4PrJEi9Tx1VoYk1as';
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Add up migration script here
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT
|
||||
FROM pg_catalog.pg_roles
|
||||
WHERE rolname = 'app') THEN
|
||||
CREATE ROLE app LOGIN PASSWORD 'changeme';
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT
|
||||
FROM pg_catalog.pg_roles
|
||||
WHERE rolname = 'admin') THEN
|
||||
CREATE ROLE admin LOGIN PASSWORD 'changeme';
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
DELETE FROM script WHERE lock IS NULL;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE completed_job
|
||||
DROP COLUMN started_at;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE completed_job
|
||||
ADD COLUMN started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW();
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE queue
|
||||
ALTER COLUMN language DROP NOT NULL;
|
||||
|
||||
ALTER TABLE completed_job
|
||||
ALTER COLUMN language DROP NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
2
backend/migrations/20220610181005_add_script_hub.up.sql
Normal file
2
backend/migrations/20220610181005_add_script_hub.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE JOB_KIND ADD VALUE 'script_hub';
|
||||
1
backend/migrations/20220620210708_regex_fix.down.sql
Normal file
1
backend/migrations/20220620210708_regex_fix.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
9
backend/migrations/20220620210708_regex_fix.up.sql
Normal file
9
backend/migrations/20220620210708_regex_fix.up.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- Add up migration script here
|
||||
|
||||
ALTER TABLE usr DROP CONSTRAINT proper_email;
|
||||
ALTER TABLE usr ADD CONSTRAINT proper_email
|
||||
CHECK (email ~* '^(?:[a-z0-9!#$%&''*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$');
|
||||
|
||||
ALTER TABLE workspace_invite DROP CONSTRAINT proper_email;
|
||||
ALTER TABLE workspace_invite ADD CONSTRAINT proper_email
|
||||
CHECK (email ~* '^(?:[a-z0-9!#$%&''*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$');
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.5.0
|
||||
version: 1.13.0
|
||||
title: Windmill server API
|
||||
contact:
|
||||
name: Windmill contact
|
||||
@@ -207,6 +207,72 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/create:
|
||||
post:
|
||||
summary: create user
|
||||
operationId: createUserGlobally
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
description: user info
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
super_admin:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
company:
|
||||
type: string
|
||||
required:
|
||||
- email
|
||||
- password
|
||||
- super_admin
|
||||
responses:
|
||||
"201":
|
||||
description: user created
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/users/update/{email}:
|
||||
post:
|
||||
summary: global update user (require super admin)
|
||||
operationId: globalUserUpdate
|
||||
tags:
|
||||
- user
|
||||
parameters:
|
||||
- name: email
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
description: new user info
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
is_super_admin:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: user updated
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/users/delete/{username}:
|
||||
delete:
|
||||
summary: delete user (require admin privilege)
|
||||
@@ -393,7 +459,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/GlobalWhoami"
|
||||
$ref: "#/components/schemas/GlobalUserInfo"
|
||||
|
||||
/users/list_invites:
|
||||
get:
|
||||
@@ -614,7 +680,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/User"
|
||||
$ref: "#/components/schemas/GlobalUserInfo"
|
||||
|
||||
/w/{workspace}/workspaces/list_pending_invites:
|
||||
get:
|
||||
@@ -1144,6 +1210,52 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
/scripts/hub/list:
|
||||
get:
|
||||
summary: list all available hub scripts
|
||||
operationId: listHubScripts
|
||||
tags:
|
||||
- script
|
||||
responses:
|
||||
"200":
|
||||
description: hub scripts list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
summary:
|
||||
type: string
|
||||
app:
|
||||
type: string
|
||||
approved:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- summary
|
||||
- app
|
||||
- approved
|
||||
|
||||
/scripts/hub/get/{path}:
|
||||
get:
|
||||
summary: get hub script content by path
|
||||
operationId: getHubScriptContentByPath
|
||||
tags:
|
||||
- script
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: script details
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/scripts/list:
|
||||
get:
|
||||
summary: list all available scripts
|
||||
@@ -1251,11 +1363,16 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
|
||||
required:
|
||||
- path
|
||||
- summary
|
||||
- description
|
||||
- content
|
||||
- language
|
||||
responses:
|
||||
"201":
|
||||
description: script created
|
||||
@@ -1264,14 +1381,35 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/scripts/tojsonschema:
|
||||
/scripts/python/tojsonschema:
|
||||
post:
|
||||
summary: inspect code to infer jsonschema of arguments
|
||||
operationId: toJsonschema
|
||||
summary: inspect python code to infer jsonschema of arguments
|
||||
operationId: pythonToJsonschema
|
||||
tags:
|
||||
- script
|
||||
requestBody:
|
||||
description: code with the main function
|
||||
description: python code with the main function
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: parsed args
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MainArgSignature"
|
||||
|
||||
/scripts/deno/tojsonschema:
|
||||
post:
|
||||
summary: inspect deno code to infer jsonschema of arguments
|
||||
operationId: denoToJsonschema
|
||||
tags:
|
||||
- script
|
||||
requestBody:
|
||||
description: deno code with the main function
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
@@ -2588,6 +2726,9 @@ components:
|
||||
type: string
|
||||
lock_error_logs:
|
||||
type: string
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
required:
|
||||
- hash
|
||||
- path
|
||||
@@ -2599,14 +2740,17 @@ components:
|
||||
- deleted
|
||||
- is_template
|
||||
- extra_perms
|
||||
- language
|
||||
|
||||
ScriptArgs:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
additionalProperties: {}
|
||||
|
||||
QueuedJob:
|
||||
type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
@@ -2661,6 +2805,9 @@ components:
|
||||
$ref: "#/components/schemas/FlowValue"
|
||||
is_flow_step:
|
||||
type: boolean
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
required:
|
||||
- id
|
||||
- running
|
||||
@@ -2672,6 +2819,8 @@ components:
|
||||
CompletedJob:
|
||||
type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
@@ -2683,6 +2832,9 @@ components:
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
duration:
|
||||
type: integer
|
||||
success:
|
||||
@@ -2723,8 +2875,15 @@ components:
|
||||
$ref: "#/components/schemas/FlowValue"
|
||||
is_flow_step:
|
||||
type: boolean
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
required:
|
||||
- id
|
||||
- created_by
|
||||
- duration
|
||||
- created_at
|
||||
- started_at
|
||||
- success
|
||||
- canceled
|
||||
- job_kind
|
||||
@@ -2958,8 +3117,22 @@ components:
|
||||
name:
|
||||
type: string
|
||||
typ:
|
||||
type: string
|
||||
enum: ["str", "float", "int", "bool", "unknown"]
|
||||
oneOf:
|
||||
- type: string
|
||||
enum: ["str", "float", "int", "bool", "email", "unknown"]
|
||||
- type: object
|
||||
properties:
|
||||
resource:
|
||||
type: string
|
||||
required:
|
||||
- resource
|
||||
- type: object
|
||||
properties:
|
||||
list:
|
||||
type: string
|
||||
enum: ["str", "float", "int", "email"]
|
||||
required:
|
||||
- list
|
||||
has_default:
|
||||
type: boolean
|
||||
default: {}
|
||||
@@ -2980,10 +3153,14 @@ components:
|
||||
type: string
|
||||
args:
|
||||
$ref: "#/components/schemas/ScriptArgs"
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
|
||||
required:
|
||||
- content
|
||||
- args
|
||||
- language
|
||||
|
||||
CreateResource:
|
||||
type: object
|
||||
@@ -3243,7 +3420,7 @@ components:
|
||||
- email
|
||||
- is_admin
|
||||
|
||||
GlobalWhoami:
|
||||
GlobalUserInfo:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
|
||||
@@ -170,6 +170,23 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"11b1586acdfc180c5a077861ee1f7201fcbcec9d0ebada464f9d952c9c3e400d": {
|
||||
"query": "INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company)\n VALUES ($1, $2, $3, 'password', $4, $5, $6)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"11eb4dd4a2c9b0b759294dde5e8b505c5a4391aa0d8cb629c665711ee0fc04a0": {
|
||||
"query": "SELECT substr(logs, $1) as logs FROM queue WHERE workspace_id = $2 AND id = $3",
|
||||
"describe": {
|
||||
@@ -419,6 +436,64 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"2420cb110a116dfbc6b8658a6d2d35db60c6aadd157488cb72eb9116ae7e9f54": {
|
||||
"query": "INSERT INTO queue\n (workspace_id, id, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"255aafff962738317f3227ae4eb871830d89b4c12c73d8dbabe6836da124e54d": {
|
||||
"query": "select path from script where hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
|
||||
"describe": {
|
||||
@@ -500,6 +575,37 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"2e11e3ef361c41e6e055dd4805cb9d5e45eaa486e35fc5f01dae311189d6800f": {
|
||||
"query": "SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "language: ScriptLang",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"37d3ee8009055e869941e548a6d5a352053a5d7782f662c34b94706488abccb6": {
|
||||
"query": "UPDATE queue SET running = false WHERE last_ping < $1 RETURNING id",
|
||||
"describe": {
|
||||
@@ -1249,6 +1355,57 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"77ba7207c8f5fd7156542cfd9943aa9a9fa87a652131c261f5020bab9ba6b5a3": {
|
||||
"query": "SELECT email, login_type::text, verified, super_admin, name, company from password LIMIT $1 OFFSET $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "login_type",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "verified",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "super_admin",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "company",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
]
|
||||
}
|
||||
},
|
||||
"7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234": {
|
||||
"query": "SELECT * FROM resource_type WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
@@ -1834,53 +1991,6 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"a151ceeddfd4a2825d4528542d3adc5c0a6947573558a2fd62429ba5da369617": {
|
||||
"query": "INSERT INTO queue\n (workspace_id, id, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Jsonb",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "job_kind",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"a1d46b44718a63d6ce5a9054d493dadbffb205500dc8fb55e9816bcdb613e0d5": {
|
||||
"query": "DELETE FROM queue WHERE schedule_path = $1",
|
||||
"describe": {
|
||||
@@ -2232,6 +2342,40 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"be33c6eb702c149044650d49b3c50493d7538d590be3f4ff6242fea85c57c667": {
|
||||
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock, language) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Int8Array",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "script_lang",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"bf1d8e043338867e1da1ed236ff6c85a566d5fd58d4b0d5c3a10454513811ba3": {
|
||||
"query": "UPDATE workspace_settings\n SET slack_team_id = null, slack_name = null WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
@@ -2713,69 +2857,6 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"ef8b14d0feb4bda6a1f9834712ab47bd21e07f0a743f74a8d1f84cb3d54bfdae": {
|
||||
"query": "SELECT * from usr LIMIT $1 OFFSET $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "workspace_id",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "email",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "is_admin",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "operator",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "role",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
}
|
||||
},
|
||||
"f056b5f3e66a764748925f1bfd3180923fde8c7fdf69088d0e4a5555cc049545": {
|
||||
"query": "SELECT result FROM completed_job WHERE id = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
@@ -2797,29 +2878,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"f12e710a0c2b2e13b98fd522028b6af8be74a9126a8207aa2974b47fd36e1845": {
|
||||
"query": "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, created_by, schema, is_template, extra_perms, lock) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Int8Array",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Jsonb",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"f325a1262084bd3468e12dc8bcc289a96536f172b679af54dd0fbc82d4d7c987": {
|
||||
"query": "DELETE FROM usr_to_group WHERE usr = $1 AND group_ = $2 AND workspace_id = $3",
|
||||
"describe": {
|
||||
|
||||
@@ -12,6 +12,7 @@ use sqlx::{query_scalar, Postgres, Transaction};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::js_eval::eval_timeout;
|
||||
use crate::scripts::{get_hub_script_by_path, ScriptLang};
|
||||
use crate::users::create_token_for_owner;
|
||||
use crate::{
|
||||
audit::{audit_log, ActionKind},
|
||||
@@ -83,6 +84,7 @@ pub struct QueuedJob {
|
||||
pub flow_status: Option<serde_json::Value>,
|
||||
pub raw_flow: Option<serde_json::Value>,
|
||||
pub is_flow_step: bool,
|
||||
pub language: Option<ScriptLang>,
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow, Serialize)]
|
||||
@@ -92,6 +94,7 @@ struct CompletedJob {
|
||||
parent_job: Option<Uuid>,
|
||||
created_by: String,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
started_at: chrono::DateTime<chrono::Utc>,
|
||||
duration: i32,
|
||||
success: bool,
|
||||
script_hash: Option<ScriptHash>,
|
||||
@@ -110,6 +113,7 @@ struct CompletedJob {
|
||||
flow_status: Option<serde_json::Value>,
|
||||
raw_flow: Option<serde_json::Value>,
|
||||
is_flow_step: bool,
|
||||
language: Option<ScriptLang>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Copy)]
|
||||
@@ -163,14 +167,11 @@ pub async fn run_job_by_path(
|
||||
) -> error::Result<(StatusCode, String)> {
|
||||
let script_path = script_path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let script_hash = get_latest_hash_for_path(&mut tx, &w_id, script_path).await?;
|
||||
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
|
||||
let (uuid, tx) = push(
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::ScriptHash {
|
||||
hash: script_hash,
|
||||
path: script_path.to_owned(),
|
||||
},
|
||||
job_payload,
|
||||
args,
|
||||
&authed.username,
|
||||
owner_to_token_owner(&authed.username, false),
|
||||
@@ -184,6 +185,25 @@ pub async fn run_job_by_path(
|
||||
Ok((StatusCode::CREATED, uuid.to_string()))
|
||||
}
|
||||
|
||||
async fn script_path_to_payload<'c>(
|
||||
script_path: &str,
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
w_id: &String,
|
||||
) -> Result<JobPayload, Error> {
|
||||
let job_payload = if script_path.starts_with("hub/") {
|
||||
JobPayload::ScriptHub {
|
||||
path: script_path.to_owned(),
|
||||
}
|
||||
} else {
|
||||
let script_hash = get_latest_hash_for_path(db, w_id, script_path).await?;
|
||||
JobPayload::ScriptHash {
|
||||
hash: script_hash,
|
||||
path: script_path.to_owned(),
|
||||
}
|
||||
};
|
||||
Ok(job_payload)
|
||||
}
|
||||
|
||||
pub async fn get_latest_hash_for_path<'c>(
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
w_id: &str,
|
||||
@@ -263,6 +283,7 @@ async fn run_preview_job(
|
||||
JobPayload::Code(RawCode {
|
||||
content: preview.content,
|
||||
path: preview.path,
|
||||
language: preview.language,
|
||||
}),
|
||||
preview.args,
|
||||
&authed.username,
|
||||
@@ -416,6 +437,7 @@ async fn list_jobs(
|
||||
"permissioned_as",
|
||||
"flow_status",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
],
|
||||
);
|
||||
let sqlc = list_completed_jobs_query(
|
||||
@@ -433,7 +455,7 @@ async fn list_jobs(
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"null as started_at",
|
||||
"started_at",
|
||||
"null as scheduled_for",
|
||||
"null as running",
|
||||
"script_hash",
|
||||
@@ -449,6 +471,7 @@ async fn list_jobs(
|
||||
"permissioned_as",
|
||||
"flow_status",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
],
|
||||
);
|
||||
let sql = format!(
|
||||
@@ -544,6 +567,7 @@ async fn list_completed_jobs(
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"started_at",
|
||||
"duration",
|
||||
"success",
|
||||
"script_hash",
|
||||
@@ -562,6 +586,7 @@ async fn list_completed_jobs(
|
||||
"null as flow_status",
|
||||
"null as raw_flow",
|
||||
"is_flow_step",
|
||||
"language",
|
||||
],
|
||||
)
|
||||
.sql()?;
|
||||
@@ -807,6 +832,7 @@ enum Job {
|
||||
#[serde(rename_all(serialize = "lowercase"))]
|
||||
pub enum JobKind {
|
||||
Script,
|
||||
Script_Hub,
|
||||
Preview,
|
||||
Dependencies,
|
||||
Flow,
|
||||
@@ -855,6 +881,7 @@ struct UnifiedJob {
|
||||
permissioned_as: String,
|
||||
flow_status: Option<serde_json::Value>,
|
||||
is_flow_step: bool,
|
||||
language: Option<ScriptLang>,
|
||||
}
|
||||
|
||||
impl From<UnifiedJob> for Job {
|
||||
@@ -866,6 +893,7 @@ impl From<UnifiedJob> for Job {
|
||||
parent_job: uj.parent_job,
|
||||
created_by: uj.created_by,
|
||||
created_at: uj.created_at,
|
||||
started_at: uj.started_at.unwrap_or(uj.created_at),
|
||||
duration: uj.duration.unwrap(),
|
||||
success: uj.success.unwrap(),
|
||||
script_hash: uj.script_hash,
|
||||
@@ -884,6 +912,7 @@ impl From<UnifiedJob> for Job {
|
||||
flow_status: uj.flow_status,
|
||||
raw_flow: None,
|
||||
is_flow_step: uj.is_flow_step,
|
||||
language: uj.language,
|
||||
}),
|
||||
"QueuedJob" => Job::QueuedJob(QueuedJob {
|
||||
workspace_id: uj.workspace_id,
|
||||
@@ -909,6 +938,7 @@ impl From<UnifiedJob> for Job {
|
||||
flow_status: uj.flow_status,
|
||||
raw_flow: None,
|
||||
is_flow_step: uj.is_flow_step,
|
||||
language: uj.language,
|
||||
}),
|
||||
t => panic!("job type {} not valid", t),
|
||||
}
|
||||
@@ -922,6 +952,7 @@ struct CancelJob {
|
||||
pub struct RawCode {
|
||||
content: String,
|
||||
path: Option<String>,
|
||||
language: ScriptLang,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -929,6 +960,7 @@ struct Preview {
|
||||
content: String,
|
||||
path: Option<String>,
|
||||
args: Option<Map<String, Value>>,
|
||||
language: ScriptLang,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -939,6 +971,9 @@ struct PreviewFlow {
|
||||
}
|
||||
|
||||
pub enum JobPayload {
|
||||
ScriptHub {
|
||||
path: String,
|
||||
},
|
||||
ScriptHash {
|
||||
hash: ScriptHash,
|
||||
path: String,
|
||||
@@ -1001,22 +1036,61 @@ pub async fn push<'c>(
|
||||
}
|
||||
}
|
||||
|
||||
let (script_hash, script_path, raw_code, job_kind, raw_flow) = match job_payload {
|
||||
let (script_hash, script_path, raw_code, job_kind, raw_flow, language) = match job_payload {
|
||||
JobPayload::ScriptHash { hash, path } => {
|
||||
(Some(hash.0), Some(path), None, JobKind::Script, None)
|
||||
}
|
||||
JobPayload::Code(RawCode { content, path }) => {
|
||||
(None, path, Some(content), JobKind::Preview, None)
|
||||
let language = sqlx::query_scalar!("SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')", hash.0, workspace_id)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
(
|
||||
Some(hash.0),
|
||||
Some(path),
|
||||
None,
|
||||
JobKind::Script,
|
||||
None,
|
||||
Some(language),
|
||||
)
|
||||
}
|
||||
JobPayload::ScriptHub { path } => (
|
||||
None,
|
||||
Some(path.clone()),
|
||||
Some(
|
||||
get_hub_script_by_path(
|
||||
Authed {
|
||||
email: Some("".to_string()),
|
||||
username: user.to_string(),
|
||||
is_admin: false,
|
||||
groups: vec![],
|
||||
},
|
||||
Path(StripPath(path)),
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
JobKind::Script_Hub,
|
||||
None,
|
||||
Some(ScriptLang::Deno),
|
||||
),
|
||||
JobPayload::Code(RawCode {
|
||||
content,
|
||||
path,
|
||||
language,
|
||||
}) => (
|
||||
None,
|
||||
path,
|
||||
Some(content),
|
||||
JobKind::Preview,
|
||||
None,
|
||||
Some(language),
|
||||
),
|
||||
JobPayload::Dependencies { hash, dependencies } => (
|
||||
Some(hash.0),
|
||||
None,
|
||||
Some(dependencies.join("\n")),
|
||||
JobKind::Dependencies,
|
||||
None,
|
||||
Some(ScriptLang::Python3),
|
||||
),
|
||||
JobPayload::RawFlow { value, path } => {
|
||||
(None, path, None, JobKind::FlowPreview, Some(value))
|
||||
(None, path, None, JobKind::FlowPreview, Some(value), None)
|
||||
}
|
||||
JobPayload::Flow(flow) => {
|
||||
let value_json = sqlx::query_scalar!("SELECT value FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
|
||||
@@ -1029,7 +1103,7 @@ pub async fn push<'c>(
|
||||
"could not convert json to flow for {flow}: {err:?}"
|
||||
))
|
||||
})?;
|
||||
(None, Some(flow), None, JobKind::Flow, Some(value))
|
||||
(None, Some(flow), None, JobKind::Flow, Some(value), None)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1043,8 +1117,8 @@ pub async fn push<'c>(
|
||||
let uuid = sqlx::query_scalar!(
|
||||
"INSERT INTO queue
|
||||
(workspace_id, id, parent_job, created_by, permissioned_as, scheduled_for,
|
||||
script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING id",
|
||||
script_hash, script_path, raw_code, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING id",
|
||||
workspace_id,
|
||||
job_id,
|
||||
parent_job,
|
||||
@@ -1059,7 +1133,8 @@ pub async fn push<'c>(
|
||||
schedule_path,
|
||||
raw_flow.map(|f| serde_json::json!(f)),
|
||||
flow_status.map(|f| serde_json::json!(f)),
|
||||
is_flow_step
|
||||
is_flow_step,
|
||||
language: ScriptLang
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
@@ -1410,12 +1485,7 @@ async fn push_next_flow_job(
|
||||
let mut tx = db.begin().await?;
|
||||
let job_payload = match &module.value {
|
||||
FlowModuleValue::Script { path: script_path } => {
|
||||
let script_hash =
|
||||
get_latest_hash_for_path(&mut tx, &job.workspace_id, script_path).await?;
|
||||
JobPayload::ScriptHash {
|
||||
hash: script_hash,
|
||||
path: script_path.to_owned(),
|
||||
}
|
||||
script_path_to_payload(script_path, &mut tx, &job.workspace_id).await?
|
||||
}
|
||||
a @ _ => {
|
||||
tracing::info!("Unrecognized module values {:?}", a);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
use ::oauth2::basic::BasicClient;
|
||||
use argon2::Argon2;
|
||||
use axum::{extract::extractor_middleware, handler::Handler, routing::get, Extension, Router};
|
||||
use axum::{handler::Handler, middleware::from_extractor, routing::get, Extension, Router};
|
||||
use db::DB;
|
||||
use git_version::git_version;
|
||||
use hyper::Response;
|
||||
@@ -215,8 +215,8 @@ pub async fn run_server(
|
||||
.nest("/workers", worker_ping::global_service())
|
||||
.nest("/scripts", scripts::global_service())
|
||||
.nest("/schedules", schedule::global_service())
|
||||
.route_layer(extractor_middleware::<users::Authed>())
|
||||
.route_layer(extractor_middleware::<users::Tokened>())
|
||||
.route_layer(from_extractor::<users::Authed>())
|
||||
.route_layer(from_extractor::<users::Tokened>())
|
||||
.nest(
|
||||
"/auth",
|
||||
users::make_unauthed_service().layer(Extension(argon2)),
|
||||
@@ -265,6 +265,7 @@ pub async fn run_workers(
|
||||
num_workers: i32,
|
||||
sleep_queue: u64,
|
||||
base_url: String,
|
||||
disable_nuser: bool,
|
||||
tx: tokio::sync::broadcast::Sender<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
let instance_name = rd_string(5);
|
||||
@@ -304,6 +305,7 @@ pub async fn run_workers(
|
||||
&ip,
|
||||
sleep_queue,
|
||||
&base_url,
|
||||
disable_nuser,
|
||||
tx,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -66,7 +66,14 @@ async fn main() -> anyhow::Result<()> {
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
.unwrap_or(windmill::DEFAULT_SLEEP_QUEUE);
|
||||
let disable_nuser = std::env::var("DISABLE_NUSER")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
tracing::info!(
|
||||
"DISABLE_NUSER: {disable_nuser}, BASE_URL: {base_url}, SLEEP_QUEUE: {sleep_queue}, NUM_WORKERS: {num_workers}, TIMEOUT: {timeout}"
|
||||
);
|
||||
windmill::run_workers(
|
||||
db.clone(),
|
||||
addr,
|
||||
@@ -74,6 +81,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
num_workers,
|
||||
sleep_queue,
|
||||
base_url,
|
||||
disable_nuser,
|
||||
tx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn global_service() -> Router {
|
||||
.route("/login_callback/:client", get(login_callback))
|
||||
.route(
|
||||
"/slack_command",
|
||||
post(slack_command).route_layer(axum::extract::extractor_middleware::<SlackSig>()),
|
||||
post(slack_command).route_layer(axum::middleware::from_extractor::<SlackSig>()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,17 @@ pub struct MainArgSignature {
|
||||
pub args: Vec<Arg>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all(serialize = "lowercase"))]
|
||||
pub enum InnerTyp {
|
||||
Str,
|
||||
Int,
|
||||
Float,
|
||||
Bytes,
|
||||
Email,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all(serialize = "lowercase"))]
|
||||
pub enum Typ {
|
||||
Str,
|
||||
@@ -33,13 +43,15 @@ pub enum Typ {
|
||||
Float,
|
||||
Bool,
|
||||
Dict,
|
||||
List,
|
||||
List(InnerTyp),
|
||||
Bytes,
|
||||
Datetime,
|
||||
Resource(String),
|
||||
Email,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Arg {
|
||||
pub name: String,
|
||||
pub typ: Typ,
|
||||
@@ -47,7 +59,7 @@ pub struct Arg {
|
||||
pub has_default: bool,
|
||||
}
|
||||
|
||||
pub fn parse_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
pub fn parse_python_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
let ast = parser::parse_program(code)
|
||||
.map_err(|e| error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string())))?
|
||||
.statements;
|
||||
@@ -94,7 +106,7 @@ pub fn parse_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
"int" => Typ::Int,
|
||||
"bool" => Typ::Bool,
|
||||
"dict" => Typ::Dict,
|
||||
"list" => Typ::List,
|
||||
"list" => Typ::List(InnerTyp::Str),
|
||||
"bytes" => Typ::Bytes,
|
||||
"datetime" => Typ::Datetime,
|
||||
"datetime.datetime" => Typ::Datetime,
|
||||
@@ -115,6 +127,188 @@ pub fn parse_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
}
|
||||
}
|
||||
|
||||
use swc_common::sync::Lrc;
|
||||
use swc_common::{FileName, SourceMap};
|
||||
use swc_ecma_ast::{
|
||||
AssignPat, BindingIdent, Decl, ExportDecl, FnDecl, Ident, ModuleDecl, ModuleItem, Pat,
|
||||
TsArrayType, TsEntityName, TsKeywordTypeKind, TsType, TsTypeRef,
|
||||
};
|
||||
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
|
||||
|
||||
pub fn parse_deno_signature(code: &str) -> error::Result<MainArgSignature> {
|
||||
let cm: Lrc<SourceMap> = Default::default();
|
||||
let fm = cm.new_source_file(FileName::Custom("test.ts".into()), code.into());
|
||||
let lexer = Lexer::new(
|
||||
// We want to parse ecmascript
|
||||
Syntax::Typescript(TsConfig::default()),
|
||||
// EsVersion defaults to es5
|
||||
Default::default(),
|
||||
StringInput::from(&*fm),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut parser = Parser::new_from(lexer);
|
||||
|
||||
let mut err_s = "".to_string();
|
||||
for e in parser.take_errors() {
|
||||
err_s += &e.into_kind().msg().to_string();
|
||||
}
|
||||
|
||||
let ast = parser
|
||||
.parse_module()
|
||||
.map_err(|e| {
|
||||
error::Error::ExecutionErr(format!("impossible to parse module: {err_s}\n{e:?}"))
|
||||
})?
|
||||
.body;
|
||||
|
||||
// println!("{ast:?}");
|
||||
let params = ast.into_iter().find_map(|x| match x {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl:
|
||||
Decl::Fn(FnDecl {
|
||||
ident:
|
||||
Ident {
|
||||
span: _,
|
||||
sym,
|
||||
optional: _,
|
||||
},
|
||||
declare: _,
|
||||
function,
|
||||
}),
|
||||
span: _,
|
||||
})) if &sym.to_string() == "main" => Some(function.params),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(params) = params {
|
||||
Ok(MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: params
|
||||
.into_iter()
|
||||
.map(|x| match x.pat {
|
||||
Pat::Ident(ident) => {
|
||||
let (name, typ) = binding_ident_to_arg(&ident)?;
|
||||
Ok(Arg {
|
||||
name,
|
||||
typ,
|
||||
default: None,
|
||||
has_default: false,
|
||||
})
|
||||
}
|
||||
Pat::Assign(AssignPat {
|
||||
span: _,
|
||||
left,
|
||||
right,
|
||||
type_ann: _,
|
||||
}) => {
|
||||
let (name, typ) =
|
||||
left.as_ident().map(binding_ident_to_arg).ok_or_else(|| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"Arg {left:?} has unexepected syntax"
|
||||
))
|
||||
})??;
|
||||
Ok(Arg {
|
||||
name,
|
||||
typ,
|
||||
default: serde_json::to_value(right)
|
||||
.map_err(|e| error::Error::ExecutionErr(e.to_string()))?
|
||||
.as_object()
|
||||
.and_then(|x| x.get("value").to_owned())
|
||||
.cloned(),
|
||||
|
||||
has_default: true,
|
||||
})
|
||||
}
|
||||
_ => Err(error::Error::ExecutionErr(format!(
|
||||
"Arg {x:?} has unexepected syntax"
|
||||
))),
|
||||
})
|
||||
.collect::<Result<Vec<Arg>, error::Error>>()?,
|
||||
})
|
||||
} else {
|
||||
Err(error::Error::ExecutionErr(
|
||||
"main function was not findable (expected to find 'export main function(...)'"
|
||||
.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn binding_ident_to_arg(
|
||||
BindingIdent { id, type_ann }: &BindingIdent,
|
||||
) -> anyhow::Result<(String, Typ)> {
|
||||
Ok((
|
||||
id.sym.to_string(),
|
||||
type_ann
|
||||
.as_ref()
|
||||
.map(|x| match &*x.type_ann {
|
||||
TsType::TsKeywordType(t) => match t.kind {
|
||||
TsKeywordTypeKind::TsObjectKeyword => Typ::Dict,
|
||||
TsKeywordTypeKind::TsBooleanKeyword => Typ::Bool,
|
||||
TsKeywordTypeKind::TsBigIntKeyword => Typ::Int,
|
||||
TsKeywordTypeKind::TsNumberKeyword => Typ::Float,
|
||||
TsKeywordTypeKind::TsStringKeyword => Typ::Str,
|
||||
_ => Typ::Unknown,
|
||||
},
|
||||
// TODO: we can do better here and extract the inner type of array
|
||||
TsType::TsArrayType(TsArrayType { span: _, elem_type }) => {
|
||||
match &**elem_type {
|
||||
TsType::TsTypeRef(TsTypeRef {
|
||||
span: _,
|
||||
type_name:
|
||||
TsEntityName::Ident(Ident {
|
||||
span: _,
|
||||
sym,
|
||||
optional: _,
|
||||
}),
|
||||
type_params: _,
|
||||
}) => match sym.to_string().as_str() {
|
||||
"Base64" => Typ::List(InnerTyp::Bytes),
|
||||
"Email" => Typ::List(InnerTyp::Email),
|
||||
"bigint" => Typ::List(InnerTyp::Int),
|
||||
"number" => Typ::List(InnerTyp::Float),
|
||||
_ => Typ::List(InnerTyp::Str),
|
||||
},
|
||||
//TsType::TsKeywordType(())
|
||||
_ => Typ::List(InnerTyp::Str),
|
||||
}
|
||||
}
|
||||
TsType::TsTypeRef(TsTypeRef {
|
||||
span: _,
|
||||
type_name,
|
||||
type_params,
|
||||
}) => {
|
||||
let sym = match type_name {
|
||||
TsEntityName::Ident(Ident {
|
||||
span: _,
|
||||
sym,
|
||||
optional: _,
|
||||
}) => sym,
|
||||
TsEntityName::TsQualifiedName(p) => &*p.right.sym,
|
||||
};
|
||||
match sym.to_string().as_str() {
|
||||
"Resource" => Typ::Resource(
|
||||
type_params
|
||||
.as_ref()
|
||||
.and_then(|x| {
|
||||
x.params.get(0).and_then(|y| {
|
||||
y.as_ts_lit_type().and_then(|z| {
|
||||
z.lit.as_str().map(|a| a.to_owned().value.to_string())
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| "unknown".to_string()),
|
||||
),
|
||||
"Base64" => Typ::Bytes,
|
||||
"Email" => Typ::Email,
|
||||
_ => Typ::Unknown,
|
||||
}
|
||||
}
|
||||
_ => Typ::Unknown,
|
||||
})
|
||||
.unwrap_or(Typ::Unknown),
|
||||
))
|
||||
}
|
||||
|
||||
const STDIMPORTS: [&str; 301] = [
|
||||
"__future__",
|
||||
"_abc",
|
||||
@@ -468,7 +662,7 @@ fn to_value(et: &ExpressionType) -> Option<serde_json::Value> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_imports(code: &str) -> error::Result<Vec<String>> {
|
||||
pub fn parse_python_imports(code: &str) -> error::Result<Vec<String>> {
|
||||
let find_requirements = code
|
||||
.lines()
|
||||
.find_position(|x| x.starts_with("#requirements:"));
|
||||
@@ -527,7 +721,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_sig() -> anyhow::Result<()> {
|
||||
fn test_parse_python_sig() -> anyhow::Result<()> {
|
||||
//let code = "print(2 + 3, fd=sys.stderr)";
|
||||
let code = "
|
||||
|
||||
@@ -540,13 +734,13 @@ def main(test1: str, name: datetime.datetime = datetime.now(), byte: bytes = byt
|
||||
return {\"len\": len(name), \"splitted\": name.split() }
|
||||
|
||||
";
|
||||
println!("{}", serde_json::to_string(&parse_signature(code)?)?);
|
||||
println!("{}", serde_json::to_string(&parse_python_signature(code)?)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_imports() -> anyhow::Result<()> {
|
||||
fn test_parse_python_imports() -> anyhow::Result<()> {
|
||||
//let code = "print(2 + 3, fd=sys.stderr)";
|
||||
let code = "
|
||||
|
||||
@@ -559,14 +753,14 @@ def main():
|
||||
pass
|
||||
|
||||
";
|
||||
let r = parse_imports(code)?;
|
||||
let r = parse_python_imports(code)?;
|
||||
println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(r, vec!["wmill", "zanzibar", "matplotlib"]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_imports2() -> anyhow::Result<()> {
|
||||
fn test_parse_python_imports2() -> anyhow::Result<()> {
|
||||
//let code = "print(2 + 3, fd=sys.stderr)";
|
||||
let code = "
|
||||
#requirements:
|
||||
@@ -583,10 +777,25 @@ def main():
|
||||
pass
|
||||
|
||||
";
|
||||
let r = parse_imports(code)?;
|
||||
let r = parse_python_imports(code)?;
|
||||
println!("{}", serde_json::to_string(&r)?);
|
||||
assert_eq!(r, vec!["burkina=0.4", "nigeria"]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_deno_sig() -> anyhow::Result<()> {
|
||||
let code = "
|
||||
|
||||
export function main(test1: string, test2: string = \"burkina\",
|
||||
test3: wmill.Resource<'postgres'>, b64: Base64, ls: Base64[], email: Email) {
|
||||
console.log(42)
|
||||
}
|
||||
|
||||
";
|
||||
println!("{}", serde_json::to_string(&parse_deno_signature(code)?)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use sql_builder::prelude::*;
|
||||
use crate::{
|
||||
audit::{audit_log, ActionKind},
|
||||
db::{UserDB, DB},
|
||||
error::{Error, JsonResult, Result},
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
jobs, parser,
|
||||
users::{owner_to_token_owner, truncate_token, Authed, Tokened},
|
||||
utils::{require_admin, Pagination, StripPath},
|
||||
@@ -35,7 +35,14 @@ use std::{
|
||||
const MAX_HASH_HISTORY_LENGTH_STORED: usize = 20;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new().route("/tojsonschema", post(parse_code_to_jsonschema))
|
||||
Router::new()
|
||||
.route(
|
||||
"/python/tojsonschema",
|
||||
post(parse_python_code_to_jsonschema),
|
||||
)
|
||||
.route("/deno/tojsonschema", post(parse_deno_code_to_jsonschema))
|
||||
.route("/hub/list", get(list_hub_scripts))
|
||||
.route("/hub/get/*path", get(get_hub_script_by_path))
|
||||
}
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -50,6 +57,13 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/deployment_status/h/:hash", get(get_deployment_status))
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug, PartialEq, Clone, Hash)]
|
||||
#[sqlx(type_name = "SCRIPT_LANG", rename_all = "lowercase")]
|
||||
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
|
||||
pub enum ScriptLang {
|
||||
Deno,
|
||||
Python3,
|
||||
}
|
||||
#[derive(sqlx::Type, PartialEq, Debug, Hash, Clone, Copy)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct ScriptHash(pub i64);
|
||||
@@ -113,6 +127,7 @@ pub struct Script {
|
||||
pub extra_perms: serde_json::Value,
|
||||
pub lock: Option<String>,
|
||||
pub lock_error_logs: Option<String>,
|
||||
pub language: ScriptLang,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, sqlx::Type, Debug)]
|
||||
@@ -138,6 +153,7 @@ pub struct NewScript {
|
||||
pub schema: Option<Schema>,
|
||||
pub is_template: Option<bool>,
|
||||
pub lock: Option<Vec<String>>,
|
||||
pub language: ScriptLang,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -181,6 +197,7 @@ async fn list_scripts(
|
||||
"extra_perms",
|
||||
"null as lock",
|
||||
"CASE WHEN lock_error_logs IS NOT NULL THEN 'error' ELSE null END as lock_error_logs",
|
||||
"language",
|
||||
])
|
||||
.order_by("created_at", lq.order_desc.unwrap_or(true))
|
||||
.and_where("workspace_id = ? OR workspace_id = 'starter'".bind(&w_id))
|
||||
@@ -226,6 +243,41 @@ async fn list_scripts(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct SearchData {
|
||||
asks: Vec<ScriptSearch>,
|
||||
}
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct ScriptSearch {
|
||||
id: i32,
|
||||
summary: String,
|
||||
app: String,
|
||||
approved: bool,
|
||||
}
|
||||
|
||||
async fn list_hub_scripts(
|
||||
Authed {
|
||||
email, username, ..
|
||||
}: Authed,
|
||||
) -> JsonResult<Vec<ScriptSearch>> {
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?;
|
||||
let rows = http_client
|
||||
.get("https://hub.windmill.dev/searchData?approved=true")
|
||||
.header("X-email", email.unwrap_or_else(|| "".to_string()))
|
||||
.header("X-username", username)
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.json::<SearchData>()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.asks;
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
fn hash_script(ns: &NewScript) -> i64 {
|
||||
let mut dh = DefaultHasher::new();
|
||||
ns.hash(&mut dh);
|
||||
@@ -344,10 +396,16 @@ async fn create_script(
|
||||
.map(|v| v.1.clone())
|
||||
.unwrap_or(json!({}));
|
||||
|
||||
let lock = if ns.language == ScriptLang::Deno {
|
||||
Some("".to_string())
|
||||
} else {
|
||||
ns.lock.as_ref().map(|x| x.join("\n"))
|
||||
};
|
||||
//::text::json is to ensure we use serde_json with preserve order
|
||||
sqlx::query!(
|
||||
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, \
|
||||
created_by, schema, is_template, extra_perms, lock) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12)",
|
||||
created_by, schema, is_template, extra_perms, lock, language) VALUES \
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13)",
|
||||
&w_id,
|
||||
&hash.0,
|
||||
ns.path,
|
||||
@@ -359,13 +417,14 @@ async fn create_script(
|
||||
ns.schema.and_then(|x| serde_json::to_string(&x.0).ok()),
|
||||
ns.is_template.unwrap_or(false),
|
||||
extra_perms,
|
||||
ns.lock.as_ref().map(|x| x.join("\n"))
|
||||
lock,
|
||||
ns.language: ScriptLang
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
let mut tx = if ns.lock.is_none() {
|
||||
let dependencies = parser::parse_imports(&ns.content)?;
|
||||
let mut tx = if ns.lock.is_none() && ns.language == ScriptLang::Python3 {
|
||||
let dependencies = parser::parse_python_imports(&ns.content)?;
|
||||
let (_, tx) = jobs::push(
|
||||
tx,
|
||||
&w_id,
|
||||
@@ -426,6 +485,34 @@ async fn create_script(
|
||||
Ok((StatusCode::CREATED, format!("{}", hash)))
|
||||
}
|
||||
|
||||
pub async fn get_hub_script_by_path(
|
||||
Authed {
|
||||
email, username, ..
|
||||
}: Authed,
|
||||
Path(path): Path<StripPath>,
|
||||
) -> Result<String> {
|
||||
let path = path
|
||||
.to_path()
|
||||
.strip_prefix("hub/")
|
||||
.ok_or_else(|| Error::BadRequest("Impossible to remove prefix hex".to_string()))?;
|
||||
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?;
|
||||
let content = http_client
|
||||
.get(format!("https://hub.windmill.dev/raw/{path}.ts"))
|
||||
.header("X-email", email.unwrap_or_else(|| "".to_string()))
|
||||
.header("X-username", username)
|
||||
.send()
|
||||
.await
|
||||
.map_err(to_anyhow)?
|
||||
.text()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
async fn get_script_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -593,10 +680,16 @@ async fn delete_script_by_hash(
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
async fn parse_code_to_jsonschema(
|
||||
async fn parse_python_code_to_jsonschema(
|
||||
Json(code): Json<String>,
|
||||
) -> JsonResult<parser::MainArgSignature> {
|
||||
parser::parse_signature(&code).map(Json)
|
||||
parser::parse_python_signature(&code).map(Json)
|
||||
}
|
||||
|
||||
async fn parse_deno_code_to_jsonschema(
|
||||
Json(code): Json<String>,
|
||||
) -> JsonResult<parser::MainArgSignature> {
|
||||
parser::parse_deno_signature(&code).map(Json)
|
||||
}
|
||||
|
||||
pub fn to_i64(s: &str) -> Result<i64> {
|
||||
|
||||
@@ -59,6 +59,7 @@ pub fn global_service() -> Router {
|
||||
.route("/accept_invite", post(accept_invite))
|
||||
.route("/list_as_super_admin", get(list_users_as_super_admin))
|
||||
.route("/setpassword", post(set_password))
|
||||
.route("/create", post(create_user))
|
||||
.route("/update/:user", post(update_user))
|
||||
.route("/logout", post(logout))
|
||||
.route("/tokens/create", post(create_token))
|
||||
@@ -316,6 +317,18 @@ pub struct User {
|
||||
pub role: Option<String>
|
||||
}
|
||||
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
pub struct GlobalUserInfo {
|
||||
email: String,
|
||||
login_type: Option<String>,
|
||||
super_admin: bool,
|
||||
verified: bool,
|
||||
name: Option<String>,
|
||||
company: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UserInfo {
|
||||
pub workspace_id: String,
|
||||
@@ -368,10 +381,10 @@ pub struct NewUser {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub super_admin: bool,
|
||||
pub name: Option<String>,
|
||||
pub company: Option<String>
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AcceptInvite {
|
||||
pub workspace_id: String,
|
||||
@@ -385,7 +398,6 @@ pub struct DeclineInvite {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EditUser {
|
||||
pub email: String,
|
||||
pub is_super_admin: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -482,12 +494,12 @@ async fn list_users_as_super_admin(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Query(pagination): Query<Pagination>
|
||||
) -> JsonResult<Vec<User>> {
|
||||
) -> JsonResult<Vec<GlobalUserInfo>> {
|
||||
let mut tx = db.begin().await?;
|
||||
require_super_admin(&mut tx, authed.email).await?;
|
||||
let (per_page, offset) = crate::utils::paginate(pagination);
|
||||
|
||||
let rows = sqlx::query_as!(User, "SELECT * from usr LIMIT $1 OFFSET $2", per_page as i32, offset as i32)
|
||||
let rows = sqlx::query_as!(GlobalUserInfo, "SELECT email, login_type::text, verified, super_admin, name, company from password LIMIT $1 OFFSET $2", per_page as i32, offset as i32)
|
||||
.fetch_all(&mut tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -588,16 +600,6 @@ async fn whoami(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
pub struct GlobalUserInfo {
|
||||
email: String,
|
||||
login_type: Option<String>,
|
||||
super_admin: bool,
|
||||
verified: bool,
|
||||
name: Option<String>,
|
||||
company: Option<String>,
|
||||
}
|
||||
|
||||
async fn global_whoami(
|
||||
Extension(db): Extension<DB>,
|
||||
Authed { email, .. }: Authed,
|
||||
@@ -837,6 +839,7 @@ async fn update_workspace_user(
|
||||
|
||||
async fn update_user(
|
||||
Authed { email, .. }: Authed,
|
||||
Path(email_to_update): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
Json(eu): Json<EditUser>,
|
||||
) -> Result<String> {
|
||||
@@ -848,7 +851,7 @@ async fn update_user(
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE password SET super_admin = $1 WHERE email = $2",
|
||||
sa,
|
||||
&eu.email
|
||||
&email_to_update
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
@@ -860,14 +863,53 @@ async fn update_user(
|
||||
"users.update",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
Some(&eu.email),
|
||||
Some(&email_to_update),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(format!("email {} updated", eu.email))
|
||||
Ok(format!("email {} updated", &email_to_update))
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(argon2): Extension<Arc<Argon2<'_>>>,
|
||||
Json(nu): Json<NewUser>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
require_super_admin(&mut tx, email.clone()).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company)
|
||||
VALUES ($1, $2, $3, 'password', $4, $5, $6)",
|
||||
&nu.email,
|
||||
true,
|
||||
&hash_password(argon2, nu.password)?,
|
||||
&nu.super_admin,
|
||||
nu.name,
|
||||
nu.company
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&email.unwrap(),
|
||||
"users.update",
|
||||
ActionKind::Update,
|
||||
"global",
|
||||
Some(&nu.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok((StatusCode::CREATED, format!("email {} created", nu.email)))
|
||||
}
|
||||
|
||||
|
||||
pub fn owner_to_token_owner(user: &str, is_group: bool) -> String {
|
||||
let prefix = if is_group { 'g' } else { 'u' };
|
||||
format!("{}/{}", prefix, user)
|
||||
@@ -909,7 +951,6 @@ async fn delete_user(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("username {} deleted", username_to_delete))
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,15 @@ pub struct Pagination {
|
||||
pub per_page: Option<usize>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct StripPath(String);
|
||||
pub struct StripPath(pub String);
|
||||
|
||||
impl StripPath {
|
||||
pub fn to_path(&self) -> &str {
|
||||
self.0.strip_prefix('/').unwrap()
|
||||
if self.0.starts_with('/') {
|
||||
self.0.strip_prefix('/').unwrap()
|
||||
} else {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::{
|
||||
QueuedJob,
|
||||
},
|
||||
parser::{self, Typ},
|
||||
scripts::ScriptHash,
|
||||
scripts::{ScriptHash, ScriptLang},
|
||||
users::{create_token_for_owner, get_email_from_username},
|
||||
variables,
|
||||
};
|
||||
@@ -44,11 +44,14 @@ use tokio::sync::mpsc;
|
||||
|
||||
const TMP_DIR: &str = "/tmp/windmill";
|
||||
const PIP_CACHE_DIR: &str = "/tmp/windmill/cache/pip";
|
||||
const DENO_CACHE_DIR: &str = "/tmp/windmill/cache/deno";
|
||||
const NUM_SECS_ENV_CHECK: u64 = 15;
|
||||
|
||||
const INCLUDE_DEPS_SH_CONTENT: &str = include_str!("../../nsjail/download_deps.sh");
|
||||
const NSJAIL_CONFIG_DOWNLOAD_CONTENT: &str = include_str!("../../nsjail/download.config.proto");
|
||||
const NSJAIL_CONFIG_RUN_CONTENT: &str = include_str!("../../nsjail/run.config.proto");
|
||||
const NSJAIL_CONFIG_RUN_PYTHON3_CONTENT: &str =
|
||||
include_str!("../../nsjail/run.python3.config.proto");
|
||||
const NSJAIL_CONFIG_RUN_DENO_CONTENT: &str = include_str!("../../nsjail/run.deno.config.proto");
|
||||
|
||||
pub async fn run_worker(
|
||||
db: &DB,
|
||||
@@ -61,22 +64,19 @@ pub async fn run_worker(
|
||||
ip: &str,
|
||||
sleep_queue: u64,
|
||||
base_url: &str,
|
||||
disable_nuser: bool,
|
||||
tx: tokio::sync::broadcast::Sender<()>,
|
||||
) {
|
||||
let worker_dir = format!("{TMP_DIR}/{worker_name}");
|
||||
tracing::debug!(worker_dir = %worker_dir, worker_name = %worker_name, "Creating worker dir");
|
||||
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(&worker_dir)
|
||||
.await
|
||||
.expect("could not create initial worker dir");
|
||||
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(&PIP_CACHE_DIR)
|
||||
.await
|
||||
.expect("could not create initial worker dir");
|
||||
for x in [&worker_dir, PIP_CACHE_DIR, DENO_CACHE_DIR] {
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(x)
|
||||
.await
|
||||
.expect("could not create initial worker dir");
|
||||
}
|
||||
|
||||
let _ = write_file(&worker_dir, "download_deps.sh", INCLUDE_DEPS_SH_CONTENT).await;
|
||||
|
||||
@@ -107,10 +107,17 @@ pub async fn run_worker(
|
||||
|
||||
tracing::info!(worker = %worker_name, id = %job.id, "Fetched job");
|
||||
let job2 = job.clone();
|
||||
if let Some(err) =
|
||||
handle_queued_job(job, db, timeout, &worker_name, &worker_dir, base_url)
|
||||
.await
|
||||
.err()
|
||||
if let Some(err) = handle_queued_job(
|
||||
job,
|
||||
db,
|
||||
timeout,
|
||||
&worker_name,
|
||||
&worker_dir,
|
||||
base_url,
|
||||
disable_nuser,
|
||||
)
|
||||
.await
|
||||
.err()
|
||||
{
|
||||
let err_string = err.to_string().clone();
|
||||
let _ = add_completed_job_error(
|
||||
@@ -162,6 +169,7 @@ async fn handle_queued_job(
|
||||
worker_name: &str,
|
||||
worker_dir: &str,
|
||||
base_url: &str,
|
||||
disable_nuser: bool,
|
||||
) -> crate::error::Result<()> {
|
||||
let job_id = job.id;
|
||||
let w_id = &job.workspace_id.clone();
|
||||
@@ -198,6 +206,7 @@ async fn handle_queued_job(
|
||||
&mut logs,
|
||||
&mut last_line,
|
||||
base_url,
|
||||
disable_nuser,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -273,6 +282,7 @@ async fn handle_job(
|
||||
mut logs: &mut String,
|
||||
mut last_line: &mut String,
|
||||
base_url: &str,
|
||||
disable_nuser: bool,
|
||||
) -> Result<JobResult, Error> {
|
||||
tracing::info!(
|
||||
worker = %worker_name,
|
||||
@@ -289,6 +299,7 @@ async fn handle_job(
|
||||
.expect("could not create initial job dir");
|
||||
|
||||
let mut status: Result<ExitStatus, Error>;
|
||||
|
||||
if matches!(job.job_kind, JobKind::Dependencies) {
|
||||
let requirements = job
|
||||
.raw_code
|
||||
@@ -344,12 +355,23 @@ async fn handle_job(
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
let (inner_content, requirements_o) = if matches!(job.job_kind, JobKind::Preview) {
|
||||
let (inner_content, requirements_o, language) = if matches!(job.job_kind, JobKind::Preview)
|
||||
|| matches!(job.job_kind, JobKind::Script_Hub)
|
||||
{
|
||||
let code = (job.raw_code.as_ref().unwrap_or(&"no raw code".to_owned())).to_owned();
|
||||
let reqs = parser::parse_imports(&code)?.join("\n");
|
||||
(code, Some(reqs))
|
||||
let reqs = if job
|
||||
.language
|
||||
.as_ref()
|
||||
.map(|x| matches!(x, ScriptLang::Python3))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(parser::parse_python_imports(&code)?.join("\n"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(code, reqs, job.language.to_owned())
|
||||
} else {
|
||||
sqlx::query_as::<_, (String, Option<String>)>("SELECT content, lock FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')")
|
||||
sqlx::query_as::<_, (String, Option<String>, Option<ScriptLang>)>("SELECT content, lock, language FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')")
|
||||
.bind(&job.script_hash.unwrap_or(ScriptHash(0)).0)
|
||||
.bind(&job.workspace_id)
|
||||
.fetch_optional(db)
|
||||
@@ -357,69 +379,82 @@ async fn handle_job(
|
||||
.ok_or_else(|| Error::InternalErr(format!("expected content and lock")))?
|
||||
};
|
||||
|
||||
let requirements =
|
||||
requirements_o.ok_or_else(|| Error::InternalErr(format!("lockfile missing")))?;
|
||||
match language {
|
||||
None => {
|
||||
return Err(Error::ExecutionErr(
|
||||
"Require language to be not null".to_string(),
|
||||
))?;
|
||||
}
|
||||
Some(ScriptLang::Python3) => {
|
||||
let requirements = requirements_o
|
||||
.ok_or_else(|| Error::InternalErr(format!("lockfile missing")))?;
|
||||
|
||||
let _ = write_file(
|
||||
&job_dir,
|
||||
"download.config.proto",
|
||||
&NSJAIL_CONFIG_DOWNLOAD_CONTENT
|
||||
.replace("{JOB_DIR}", &job_dir)
|
||||
.replace("{WORKER_DIR}", &worker_dir)
|
||||
.replace("{CACHE_DIR}", PIP_CACHE_DIR),
|
||||
)
|
||||
.await?;
|
||||
let _ = write_file(&job_dir, "requirements.txt", &requirements).await?;
|
||||
let _ = write_file(
|
||||
&job_dir,
|
||||
"download.config.proto",
|
||||
&NSJAIL_CONFIG_DOWNLOAD_CONTENT
|
||||
.replace("{JOB_DIR}", &job_dir)
|
||||
.replace("{WORKER_DIR}", &worker_dir)
|
||||
.replace("{CACHE_DIR}", PIP_CACHE_DIR)
|
||||
.replace("{CLONE_NEWUSER}", &(!disable_nuser).to_string()),
|
||||
)
|
||||
.await?;
|
||||
let _ = write_file(&job_dir, "requirements.txt", &requirements).await?;
|
||||
|
||||
let child = Command::new("nsjail")
|
||||
.current_dir(&job_dir)
|
||||
.args(vec!["--config", "download.config.proto"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
let child = Command::new("nsjail")
|
||||
.current_dir(&job_dir)
|
||||
.args(vec!["--config", "download.config.proto"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
logs.push_str("\n--- DEPENDENCIES INSTALL ---\n");
|
||||
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
|
||||
if status.is_ok() {
|
||||
logs.push_str("\n\n--- CODE EXECUTION ---\n");
|
||||
logs.push_str("\n--- PIP DEPENDENCIES INSTALL ---\n");
|
||||
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
|
||||
|
||||
set_logs(logs, job.id, db).await;
|
||||
if status.is_ok() {
|
||||
logs.push_str("\n\n--- PTHON CODE EXECUTION ---\n");
|
||||
|
||||
let _ = write_file(&job_dir, "inner.py", &inner_content).await?;
|
||||
set_logs(logs, job.id, db).await;
|
||||
|
||||
let sig = crate::parser::parse_signature(&inner_content)?;
|
||||
let transforms = sig.args.into_iter().map(|x| match x.typ {
|
||||
let _ = write_file(&job_dir, "inner.py", &inner_content).await?;
|
||||
|
||||
let sig = crate::parser::parse_python_signature(&inner_content)?;
|
||||
let transforms = sig.args.into_iter().map(|x| match x.typ {
|
||||
Typ::Bytes => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = base64.b64decode(kwargs[\"{}\"])\n", x.name, x.name, x.name, x.name),
|
||||
Typ::Datetime => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = datetime.strptime(kwargs[\"{}\"], '%Y-%m-%dT%H:%M')\n", x.name, x.name, x.name, x.name),
|
||||
_ => "".to_string()
|
||||
}).collect::<Vec<String>>().join("");
|
||||
|
||||
let tx = db.begin().await?;
|
||||
let tx = db.begin().await?;
|
||||
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
crate::users::NewToken {
|
||||
label: Some("ephemeral-script".to_string()),
|
||||
expiration: Some(
|
||||
chrono::Utc::now() + chrono::Duration::seconds((timeout * 2).into()),
|
||||
),
|
||||
},
|
||||
&job.created_by,
|
||||
)
|
||||
.await?;
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
crate::users::NewToken {
|
||||
label: Some("ephemeral-script".to_string()),
|
||||
expiration: Some(
|
||||
chrono::Utc::now()
|
||||
+ chrono::Duration::seconds((timeout * 2).into()),
|
||||
),
|
||||
},
|
||||
&job.created_by,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let args = if let Some(args) = &job.args {
|
||||
Some(transform_json_value(&token, &job.workspace_id, base_url, args.clone()).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ser_args = serde_json::to_string(&args)
|
||||
.map_err(|e| Error::ExecutionErr(e.to_string()))?
|
||||
.replace("\\\"", "\\\\\"");
|
||||
let wrapper_content: String = format!(
|
||||
r#"
|
||||
let args = if let Some(args) = &job.args {
|
||||
Some(
|
||||
transform_json_value(&token, &job.workspace_id, base_url, args.clone())
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ser_args = serde_json::to_string(&args)
|
||||
.map_err(|e| Error::ExecutionErr(e.to_string()))?
|
||||
.replace("\\\"", "\\\\\"");
|
||||
let wrapper_content: String = format!(
|
||||
r#"
|
||||
import json
|
||||
import base64
|
||||
from datetime import datetime
|
||||
@@ -443,44 +478,153 @@ print()
|
||||
print("result:")
|
||||
print(res_json)
|
||||
"#,
|
||||
);
|
||||
write_file(&job_dir, "main.py", &wrapper_content).await?;
|
||||
);
|
||||
write_file(&job_dir, "main.py", &wrapper_content).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
let reserved_variables = variables::get_reserved_variables(
|
||||
&job.workspace_id,
|
||||
&token,
|
||||
&get_email_from_username(&job.created_by, db)
|
||||
.await?
|
||||
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
|
||||
&job.created_by,
|
||||
&job.id.to_string(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|rv| (rv.name, rv.value));
|
||||
tx.commit().await?;
|
||||
let reserved_variables = variables::get_reserved_variables(
|
||||
&job.workspace_id,
|
||||
&token,
|
||||
&get_email_from_username(&job.created_by, db)
|
||||
.await?
|
||||
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
|
||||
&job.created_by,
|
||||
&job.id.to_string(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|rv| (rv.name, rv.value));
|
||||
let _ = write_file(
|
||||
&job_dir,
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_PYTHON3_CONTENT
|
||||
.replace("{JOB_DIR}", &job_dir)
|
||||
.replace("{CLONE_NEWUSER}", &(!disable_nuser).to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = write_file(
|
||||
&job_dir,
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_CONTENT.replace("{JOB_DIR}", &job_dir),
|
||||
)
|
||||
.await?;
|
||||
let child = Command::new("nsjail")
|
||||
.current_dir(&job_dir)
|
||||
.envs(reserved_variables)
|
||||
.args(vec![
|
||||
"--config",
|
||||
"run.config.proto",
|
||||
"--",
|
||||
"/usr/local/bin/python3",
|
||||
"-u",
|
||||
"/tmp/main.py",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
|
||||
}
|
||||
}
|
||||
Some(ScriptLang::Deno) => {
|
||||
logs.push_str("\n\n--- DENO CODE EXECUTION ---\n");
|
||||
|
||||
let child = Command::new("nsjail")
|
||||
.current_dir(&job_dir)
|
||||
.envs(reserved_variables)
|
||||
.args(vec![
|
||||
"--config",
|
||||
set_logs(logs, job.id, db).await;
|
||||
|
||||
let _ = write_file(&job_dir, "inner.ts", &inner_content).await?;
|
||||
|
||||
let sig = crate::parser::parse_deno_signature(&inner_content)?;
|
||||
// let transforms = sig.args.clone().into_iter().map(|x| match x.typ {
|
||||
// Typ::Bytes => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = base64.b64decode(kwargs[\"{}\"])\n", x.name, x.name, x.name, x.name),
|
||||
// Typ::Datetime => format!("if \"{}\" in kwargs and kwargs[\"{}\"] is not None:\n kwargs[\"{}\"] = datetime.strptime(kwargs[\"{}\"], '%Y-%m-%dT%H:%M')\n", x.name, x.name, x.name, x.name),
|
||||
// _ => "".to_string()
|
||||
// }).collect::<Vec<String>>().join("");
|
||||
|
||||
let tx = db.begin().await?;
|
||||
|
||||
let token = create_token_for_owner(
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
&job.permissioned_as,
|
||||
crate::users::NewToken {
|
||||
label: Some("ephemeral-script".to_string()),
|
||||
expiration: Some(
|
||||
chrono::Utc::now() + chrono::Duration::seconds((timeout * 2).into()),
|
||||
),
|
||||
},
|
||||
&job.created_by,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let args = if let Some(args) = &job.args {
|
||||
Some(
|
||||
transform_json_value(&token, &job.workspace_id, base_url, args.clone())
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ser_args = serde_json::to_string(&args)
|
||||
.map_err(|e| Error::ExecutionErr(e.to_string()))?
|
||||
.replace("\\\"", "\\\\\"");
|
||||
let spread = sig.args.into_iter().map(|x| x.name).join(",");
|
||||
let wrapper_content: String = format!(
|
||||
r#"
|
||||
import {{ main }} from "./inner.ts";
|
||||
const {{{spread}}} = JSON.parse(`{ser_args}`);
|
||||
|
||||
async function run() {{
|
||||
let res: any = await main({spread});
|
||||
if (res == undefined) {{
|
||||
res = {{}}
|
||||
}}
|
||||
if (typeof res !== 'object') {{
|
||||
res = {{ res1: res }}
|
||||
}}
|
||||
|
||||
const res_json = JSON.stringify(res);
|
||||
console.log();
|
||||
console.log("result:");
|
||||
console.log(res_json);
|
||||
}}
|
||||
run();
|
||||
"#,
|
||||
);
|
||||
write_file(&job_dir, "main.ts", &wrapper_content).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
let reserved_variables = variables::get_reserved_variables(
|
||||
&job.workspace_id,
|
||||
&token,
|
||||
&get_email_from_username(&job.created_by, db)
|
||||
.await?
|
||||
.unwrap_or_else(|| "nosuitable@email.xyz".to_string()),
|
||||
&job.created_by,
|
||||
&job.id.to_string(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|rv| (rv.name, rv.value));
|
||||
let _ = write_file(
|
||||
&job_dir,
|
||||
"run.config.proto",
|
||||
"--",
|
||||
"/usr/local/bin/python3",
|
||||
"-u",
|
||||
"/tmp/main.py",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
|
||||
&NSJAIL_CONFIG_RUN_DENO_CONTENT
|
||||
.replace("{JOB_DIR}", &job_dir)
|
||||
.replace("{CACHE_DIR}", DENO_CACHE_DIR)
|
||||
.replace("{CLONE_NEWUSER}", &(!disable_nuser).to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let child = Command::new("nsjail")
|
||||
.current_dir(&job_dir)
|
||||
.envs(reserved_variables)
|
||||
.args(vec![
|
||||
"--config",
|
||||
"run.config.proto",
|
||||
"--",
|
||||
"/usr/bin/deno",
|
||||
"run",
|
||||
"--v8-flags=--max-heap-size=2048",
|
||||
"-A",
|
||||
"/tmp/main.ts",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
status = handle_child(job, db, &mut logs, &mut last_line, timeout, child).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::fs::remove_dir_all(job_dir).await?;
|
||||
|
||||
BIN
backend/v8.snap
BIN
backend/v8.snap
Binary file not shown.
1
deno-client/.gitignore
vendored
Normal file
1
deno-client/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
windmill-api
|
||||
5
deno-client/.vscode/settings.json
vendored
Normal file
5
deno-client/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"deno.unstable": true
|
||||
}
|
||||
5
deno-client/generate.sh
Executable file
5
deno-client/generate.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
/usr/local/bin/docker-entrypoint.sh generate -i ../backend/openapi.yaml -g typescript --additional-properties platform=deno -o windmill-api
|
||||
sed -i 's/this\.type = "Job";//' windmill-api/models/Job.ts
|
||||
69
deno-client/index.ts
Normal file
69
deno-client/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ResourceApi, VariableApi, ServerConfiguration } from './windmill-api/index.ts'
|
||||
import { createConfiguration, type Configuration as Configuration } from './windmill-api/configuration.ts'
|
||||
|
||||
export {
|
||||
AdminApi, AuditApi, FlowApi, GranularAclApi, GroupApi,
|
||||
JobApi, ResourceApi, VariableApi, ScriptApi, ScheduleApi, SettingsApi,
|
||||
UserApi, WorkspaceApi
|
||||
} from './windmill-api/index.ts'
|
||||
|
||||
export type Email = string
|
||||
export type Base64 = string
|
||||
export type Resource<S extends string> = {}
|
||||
|
||||
/**
|
||||
* Create a client configuration from env variables
|
||||
* @returns client configuration
|
||||
*/
|
||||
export function createConf(): Configuration & { workspace_id: string } {
|
||||
const token = Deno.env.get("WM_TOKEN") ?? 'no_token'
|
||||
const base_url = Deno.env.get("BASE_INTERNAL_URL") ?? 'http://localhost:8000'
|
||||
return {
|
||||
...createConfiguration({
|
||||
baseServer: new ServerConfiguration(`${base_url}/api`, {}),
|
||||
authMethods: { bearerAuth: { tokenProvider: { getToken() { return token } } } },
|
||||
}), workspace_id: Deno.env.get("WM_WORKSPACE") ?? 'no_workspace'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource value by path
|
||||
* @param path path of the resource
|
||||
* @returns resource value
|
||||
*/
|
||||
export async function getResource(path: string): Promise<any> {
|
||||
const conf = createConf()
|
||||
const resource = await new ResourceApi(conf).getResource(conf.workspace_id, path)
|
||||
return await transformLeaves(resource.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a variable by path
|
||||
* @param path path of the variable
|
||||
* @returns variable value
|
||||
*/
|
||||
export async function getVariable(path: string): Promise<string | undefined> {
|
||||
const conf = createConf()
|
||||
const variable = await new VariableApi(conf).getVariable(conf.workspace_id, path)
|
||||
return variable.value
|
||||
}
|
||||
|
||||
async function transformLeaves(d: { [key: string]: any }): Promise<{ [key: string]: any }> {
|
||||
for (const k in d) {
|
||||
d[k] = await _transformLeaf(d[k])
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
const VAR_RESOURCE_PREFIX = "$var:"
|
||||
async function _transformLeaf(v: any): Promise<any> {
|
||||
if (typeof v === 'object') {
|
||||
return transformLeaves(v)
|
||||
}
|
||||
else if (typeof v === 'string' && v.startsWith(VAR_RESOURCE_PREFIX)) {
|
||||
const varName = v.substring(VAR_RESOURCE_PREFIX.length)
|
||||
return await getVariable(varName)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
7
deno-client/openapitools.json
Normal file
7
deno-client/openapitools.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "6.0.0-beta"
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,11 @@ version: '3.7'
|
||||
services:
|
||||
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
restart: always
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
- ./init-db.sql:/docker-entrypoint-initdb.d/create_tables.sql
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
@@ -18,30 +19,26 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
windmill:
|
||||
image: windmill:main
|
||||
image: ghcr.io/windmill-labs/windmill:main
|
||||
privileged: true
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
- 80:8000
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
|
||||
- VARIABLES_KEY=changeme
|
||||
- APP_USER_PASSWORD=changeme
|
||||
- BASE_URL=http://localhost
|
||||
- BASE_INTERNAL_URL=http://localhost:8000
|
||||
- RUST_LOG=info
|
||||
- NUM_WORKERS=3
|
||||
- RUST_BACKTRACE=1
|
||||
- GITHUB_OAUTH_CLIENT_ID=${GITHUB_OAUTH_CLIENT_ID}
|
||||
- GITHUB_OAUTH_CLIENT_SECRET=${GITHUB_OAUTH_CLIENT_SECRET}
|
||||
- DISABLE_NUSER=false
|
||||
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
caddy:
|
||||
image: caddy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SITE_URL=${SITE_URL}
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- $PWD/Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
external: true
|
||||
|
||||
db_data: null
|
||||
|
||||
@@ -20,4 +20,4 @@ module.exports = {
|
||||
rules: {
|
||||
'no-console': ['log', { allow: ['warn', 'error'] }]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@@ -5,4 +5,4 @@ node_modules
|
||||
/package
|
||||
/src/gen
|
||||
CaddyfileRemoteRuben
|
||||
|
||||
cypress/videos
|
||||
1
frontend/.nvmrc
Normal file
1
frontend/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
v17.9.0
|
||||
@@ -2,5 +2,6 @@
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100
|
||||
"printWidth": 100,
|
||||
"semi": false
|
||||
}
|
||||
|
||||
@@ -5,4 +5,8 @@
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /api/* http://localhost:8000
|
||||
reverse_proxy /* http://localhost:3000
|
||||
reverse_proxy /ws/* http://localhost:3001 {
|
||||
lb_policy header "Authorization"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
http://localhost {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /api/* https://demo.windmill.dev {
|
||||
header_up Host {http.reverse_proxy.upstream.hostport}
|
||||
}
|
||||
reverse_proxy /* http://localhost:3000
|
||||
}
|
||||
|
||||
|
||||
https://localhost {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* https://demo.windmill.dev {
|
||||
header_up Host {http.reverse_proxy.upstream.hostport}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
http://localhost {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /api/* https://alpha.windmill.dev {
|
||||
reverse_proxy /api/* https://app.windmill.dev {
|
||||
header_up Host {http.reverse_proxy.upstream.hostport}
|
||||
}
|
||||
reverse_proxy /* http://localhost:3000
|
||||
@@ -9,7 +9,7 @@ http://localhost {
|
||||
|
||||
https://localhost {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* https://alpha.windmill.dev {
|
||||
reverse_proxy /ws/* https://app.windmill.dev {
|
||||
header_up Host {http.reverse_proxy.upstream.hostport}
|
||||
}
|
||||
}
|
||||
|
||||
6
frontend/cypress.json
Normal file
6
frontend/cypress.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"pluginsFile": false,
|
||||
"env": {
|
||||
"baseUrl": "http://localhost:8000"
|
||||
}
|
||||
}
|
||||
18
frontend/cypress/integration/authentication.spec.ts
Normal file
18
frontend/cypress/integration/authentication.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// TODO: Should correctly handle exceptions
|
||||
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
// returning false here prevents Cypress from
|
||||
// failing the test
|
||||
return false
|
||||
})
|
||||
|
||||
describe('Authentication', () => {
|
||||
it('can login using email and password', () => {
|
||||
cy.login('admin@windmill.dev', 'changeme')
|
||||
cy.contains('Select a workspace')
|
||||
})
|
||||
|
||||
it('should redirect to login page if user is not logged in', () => {
|
||||
cy.visit(`${Cypress.env('baseUrl')}/user/workspaces`)
|
||||
cy.url().should('include', '/user/login')
|
||||
})
|
||||
})
|
||||
7
frontend/cypress/support/commands.ts
Normal file
7
frontend/cypress/support/commands.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
Cypress.Commands.add('login', (email: string, password: string) => {
|
||||
cy.visit(`${Cypress.env('baseUrl')}/user/login`)
|
||||
cy.get('#showPassword').click()
|
||||
cy.get('#email').type('admin@windmill.dev')
|
||||
cy.get('#password').type('changeme')
|
||||
cy.get('.flex > .default-button').click()
|
||||
})
|
||||
8
frontend/cypress/support/index.ts
Normal file
8
frontend/cypress/support/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import './commands'
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
login: (email: string, password: string) => Chainable<Element>
|
||||
}
|
||||
}
|
||||
}
|
||||
8
frontend/cypress/tsconfig.json
Normal file
8
frontend/cypress/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
4716
frontend/package-lock.json
generated
4716
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.5.0",
|
||||
"version": "1.13.0",
|
||||
"scripts": {
|
||||
"dev": "svelte-kit dev",
|
||||
"build": "svelte-kit build",
|
||||
@@ -9,49 +9,51 @@
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
||||
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .",
|
||||
"generate-backend-client": "openapi --input ../backend/openapi.yaml --output ./src/gen --useOptions"
|
||||
"generate-backend-client": "openapi --input ../backend/openapi.yaml --output ./src/gen --useOptions",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.55",
|
||||
"@sveltejs/adapter-static": "^1.0.0-next.21",
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.78",
|
||||
"@sveltejs/adapter-static": "^1.0.0-next.34",
|
||||
"@sveltejs/kit": "next",
|
||||
"@tailwindcss/forms": "^0.4.0",
|
||||
"@tailwindcss/forms": "^0.5.1",
|
||||
"@tailwindcss/typography": "^0.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
||||
"@typescript-eslint/parser": "^4.31.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"autoprefixer": "^10.4.1",
|
||||
"cssnano": "^5.0.8",
|
||||
"eslint": "^7.32.0",
|
||||
"cssnano": "^5.1.10",
|
||||
"cypress": "^9.7.0",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^3.2.1",
|
||||
"openapi-typescript-codegen": "^0.11.8",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"openapi-typescript-codegen": "^0.22.0",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss-load-config": "^3.1.0",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.4.1",
|
||||
"prettier-plugin-svelte": "^2.4.0",
|
||||
"simple-svelte-autocomplete": "^2.2.4",
|
||||
"stylelint-config-recommended": "^6.0.0",
|
||||
"stylelint-config-recommended": "^7.0.0",
|
||||
"svelte": "^3.42.6",
|
||||
"svelte-awesome": "^2.4.2",
|
||||
"svelte-check": "^2.2.6",
|
||||
"svelte-highlight": "^5.1.3",
|
||||
"svelte-highlight": "^6.0.1",
|
||||
"svelte-preprocess": "^4.9.8",
|
||||
"tailwindcss": "^3.0.11",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^4.7.2"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||
"@codingame/monaco-languageclient": "^0.17.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@codingame/monaco-jsonrpc": "^0.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@types/vscode": "^1.63.0",
|
||||
"@zerodevx/svelte-toast": "^0.6.2",
|
||||
"@zerodevx/svelte-toast": "^0.7.2",
|
||||
"fuse.js": "^6.4.6",
|
||||
"highlight.js": "^11.5.1",
|
||||
"monaco-editor": "^0.30.0",
|
||||
"svelte-awesome": "^2.4.2",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"monaco-languageclient": "^1.0.1",
|
||||
"svelte-awesome": "^3.0.0",
|
||||
"svelte-markdown": "^0.2.1",
|
||||
"svelte-split-pane": "^0.1.2"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const tailwindcss = require('tailwindcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssnano = require('cssnano');
|
||||
const tailwindcss = require('tailwindcss')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const cssnano = require('cssnano')
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === 'development';
|
||||
const mode = process.env.NODE_ENV
|
||||
const dev = mode === 'development'
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
@@ -16,6 +16,6 @@ const config = {
|
||||
preset: 'default'
|
||||
})
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
module.exports = config
|
||||
|
||||
2
frontend/src/.d.ts
vendored
2
frontend/src/.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
declare namespace svelte.JSX {
|
||||
interface DOMAttributes<T> {
|
||||
onclick_outside?: CompositionEventHandler<T>;
|
||||
onclick_outside?: CompositionEventHandler<T>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<link rel="icon" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>windmill.dev</title>
|
||||
%svelte.head%
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body class="outline-none focus:outline-none">
|
||||
%svelte.body%
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
export function pathToMeta(path) {
|
||||
const splitted = path.split('/');
|
||||
let ownerKind;
|
||||
if (splitted[0] == 'g') {
|
||||
ownerKind = 'group';
|
||||
}
|
||||
else if (splitted[0] == 'u') {
|
||||
ownerKind = 'user';
|
||||
}
|
||||
else {
|
||||
console.error('Not recognized owner:' + splitted[0]);
|
||||
return {
|
||||
ownerKind: 'user',
|
||||
owner: '',
|
||||
name: ''
|
||||
};
|
||||
}
|
||||
return {
|
||||
ownerKind,
|
||||
owner: splitted[1],
|
||||
name: splitted.slice(2).join('/')
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=common.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"common.js","sourceRoot":"","sources":["common.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChC,IAAI,SAAoB,CAAA;IACxB,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE;QACvB,SAAS,GAAG,OAAO,CAAA;KACnB;SAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE;QAC9B,SAAS,GAAG,MAAM,CAAA;KAClB;SAAM;QACN,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QACpD,OAAO;YACN,SAAS,EAAE,MAAM;YACjB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,EAAE;SACR,CAAA;KACD;IACD,OAAO;QACN,SAAS;QACT,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QAClB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;KACjC,CAAA;AACF,CAAC"}
|
||||
@@ -2,16 +2,15 @@ export type OwnerKind = 'group' | 'user'
|
||||
|
||||
export type ActionKind = 'Create' | 'Update' | 'Delete' | 'Execute'
|
||||
|
||||
|
||||
export interface SchemaProperty {
|
||||
type: string | undefined
|
||||
description: string
|
||||
pattern?: string
|
||||
default?: any
|
||||
enum?: string[]
|
||||
contentEncoding?: "base64" | "binary"
|
||||
contentEncoding?: 'base64' | 'binary'
|
||||
format?: string
|
||||
items?: { type?: "string" | "number" }
|
||||
items?: { type?: 'string' | 'number' | 'bytes', contentEncoding?: 'base64' },
|
||||
}
|
||||
|
||||
export type Schema = {
|
||||
@@ -23,7 +22,6 @@ export type Schema = {
|
||||
|
||||
export type Meta = { ownerKind: OwnerKind; owner: string; name: string }
|
||||
|
||||
|
||||
export function pathToMeta(path: string): Meta {
|
||||
const splitted = path.split('/')
|
||||
let ownerKind: OwnerKind
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
export async function handle({ event, resolve }) {
|
||||
const response = await resolve(event, {
|
||||
ssr: false
|
||||
});
|
||||
return response;
|
||||
}
|
||||
//# sourceMappingURL=hooks.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["hooks.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE;IAC3C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE;QAClC,GAAG,EAAE,KAAK;KACb,CAAC,CAAA;IAEF,OAAO,QAAQ,CAAA;AACnB,CAAC"}
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @type {import('@sveltejs/kit').Handle} */
|
||||
export async function handle({ event, resolve }) {
|
||||
const response = await resolve(event, {
|
||||
ssr: false
|
||||
})
|
||||
const response = await resolve(event, {
|
||||
ssr: false
|
||||
})
|
||||
|
||||
return response
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { ScriptService } from "./gen";
|
||||
import { sendUserToast } from "./utils";
|
||||
export async function inferArgs(code, schema) {
|
||||
try {
|
||||
const inferedSchema = await ScriptService.toJsonschema({
|
||||
requestBody: code
|
||||
});
|
||||
schema.required = [];
|
||||
const oldProperties = Object.assign({}, schema.properties);
|
||||
schema.properties = {};
|
||||
for (const arg of inferedSchema.args) {
|
||||
if (!(arg.name in oldProperties)) {
|
||||
schema.properties[arg.name] = { description: '', type: '' };
|
||||
}
|
||||
else {
|
||||
schema.properties[arg.name] = oldProperties[arg.name];
|
||||
}
|
||||
pythonToJsonSchemaType(arg.typ, schema.properties[arg.name]);
|
||||
schema.properties[arg.name].default = arg.default;
|
||||
if (!arg.has_default) {
|
||||
schema.required.push(arg.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true);
|
||||
}
|
||||
}
|
||||
function array_move(arr, fromIndex, toIndex) {
|
||||
var element = arr[fromIndex];
|
||||
arr.splice(fromIndex, 1);
|
||||
arr.splice(toIndex, 0, element);
|
||||
}
|
||||
function pythonToJsonSchemaType(t, s) {
|
||||
if (t === 'int') {
|
||||
s.type = 'integer';
|
||||
}
|
||||
else if (t === 'float') {
|
||||
s.type = 'number';
|
||||
}
|
||||
else if (t === 'bool') {
|
||||
s.type = 'boolean';
|
||||
}
|
||||
else if (t === 'str') {
|
||||
s.type = 'string';
|
||||
}
|
||||
else if (t === 'dict') {
|
||||
s.type = 'object';
|
||||
}
|
||||
else if (t === 'list') {
|
||||
s.type = 'array';
|
||||
}
|
||||
else if (t === 'bytes') {
|
||||
s.type = 'string';
|
||||
s.contentEncoding = 'base64';
|
||||
}
|
||||
else if (t === 'datetime') {
|
||||
s.type = 'string';
|
||||
s.format = 'date-time';
|
||||
}
|
||||
else {
|
||||
s.type = undefined;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=infer.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"infer.js","sourceRoot":"","sources":["infer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEvC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,MAAc;IACxD,IAAI;QACA,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,YAAY,CAAC;YACnD,WAAW,EAAE,IAAI;SACpB,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAA;QACpB,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;QAC1D,MAAM,CAAC,UAAU,GAAG,EAAE,CAAA;QAEtB,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,IAAI,EAAE;YAClC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,aAAa,CAAC,EAAE;gBAC9B,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;aAC9D;iBAAM;gBACH,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;aACxD;YACD,sBAAsB,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;YAC5D,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;YAEjD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE;gBAClB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;aACjC;SACJ;KACJ;IAAC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAClB,aAAa,CAAC,2BAA2B,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,EAAE,IAAI,CAAC,CAAA;KACpE;AACL,CAAC;AAED,SAAS,UAAU,CAAI,GAAQ,EAAE,SAAiB,EAAE,OAAe;IAC/D,IAAI,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5B,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;IACxB,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,sBAAsB,CAAC,CAAS,EAAE,CAAiB;IACxD,IAAI,CAAC,KAAK,KAAK,EAAE;QACb,CAAC,CAAC,IAAI,GAAG,SAAS,CAAA;KACrB;SAAM,IAAI,CAAC,KAAK,OAAO,EAAE;QACtB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;KACpB;SAAM,IAAI,CAAC,KAAK,MAAM,EAAE;QACrB,CAAC,CAAC,IAAI,GAAG,SAAS,CAAA;KACrB;SAAM,IAAI,CAAC,KAAK,KAAK,EAAE;QACpB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;KACpB;SAAM,IAAI,CAAC,KAAK,MAAM,EAAE;QACrB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;KACpB;SAAM,IAAI,CAAC,KAAK,MAAM,EAAE;QACrB,CAAC,CAAC,IAAI,GAAG,OAAO,CAAA;KACnB;SAAM,IAAI,CAAC,KAAK,OAAO,EAAE;QACtB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;QACjB,CAAC,CAAC,eAAe,GAAG,QAAQ,CAAA;KAC/B;SAAM,IAAI,CAAC,KAAK,UAAU,EAAE;QACzB,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAA;QACjB,CAAC,CAAC,MAAM,GAAG,WAAW,CAAA;KACzB;SAAM;QACH,CAAC,CAAC,IAAI,GAAG,SAAS,CAAA;KACrB;AACL,CAAC"}
|
||||
@@ -1,61 +1,77 @@
|
||||
import type { Schema, SchemaProperty } from "./common"
|
||||
import { ScriptService } from "./gen"
|
||||
import { sendUserToast } from "./utils"
|
||||
import type { Schema, SchemaProperty } from './common'
|
||||
import { ScriptService, type MainArgSignature } from './gen'
|
||||
import { sendUserToast } from './utils'
|
||||
|
||||
export async function inferArgs(code: string, schema: Schema): Promise<void> {
|
||||
try {
|
||||
const inferedSchema = await ScriptService.toJsonschema({
|
||||
requestBody: code
|
||||
})
|
||||
schema.required = []
|
||||
const oldProperties = Object.assign({}, schema.properties)
|
||||
schema.properties = {}
|
||||
export async function inferArgs(
|
||||
language: 'python3' | 'deno',
|
||||
code: string,
|
||||
schema: Schema
|
||||
): Promise<void> {
|
||||
try {
|
||||
let inferedSchema: MainArgSignature
|
||||
if (language == 'python3') {
|
||||
inferedSchema = await ScriptService.pythonToJsonschema({
|
||||
requestBody: code
|
||||
})
|
||||
} else if (language == 'deno') {
|
||||
inferedSchema = await ScriptService.denoToJsonschema({
|
||||
requestBody: code
|
||||
})
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
for (const arg of inferedSchema.args) {
|
||||
if (!(arg.name in oldProperties)) {
|
||||
schema.properties[arg.name] = { description: '', type: '' }
|
||||
} else {
|
||||
schema.properties[arg.name] = oldProperties[arg.name]
|
||||
}
|
||||
pythonToJsonSchemaType(arg.typ, schema.properties[arg.name])
|
||||
schema.properties[arg.name].default = arg.default
|
||||
schema.required = []
|
||||
const oldProperties = Object.assign({}, schema.properties)
|
||||
schema.properties = {}
|
||||
|
||||
if (!arg.has_default) {
|
||||
schema.required.push(arg.name)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true)
|
||||
}
|
||||
for (const arg of inferedSchema.args) {
|
||||
if (!(arg.name in oldProperties)) {
|
||||
schema.properties[arg.name] = { description: '', type: '' }
|
||||
} else {
|
||||
schema.properties[arg.name] = oldProperties[arg.name]
|
||||
}
|
||||
argSigToJsonSchemaType(arg.typ, schema.properties[arg.name])
|
||||
schema.properties[arg.name].default = arg.default
|
||||
|
||||
if (!arg.has_default) {
|
||||
schema.required.push(arg.name)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
function array_move<T>(arr: T[], fromIndex: number, toIndex: number) {
|
||||
var element = arr[fromIndex]
|
||||
arr.splice(fromIndex, 1)
|
||||
arr.splice(toIndex, 0, element)
|
||||
}
|
||||
|
||||
function pythonToJsonSchemaType(t: string, s: SchemaProperty): void {
|
||||
if (t === 'int') {
|
||||
s.type = 'integer'
|
||||
} else if (t === 'float') {
|
||||
s.type = 'number'
|
||||
} else if (t === 'bool') {
|
||||
s.type = 'boolean'
|
||||
} else if (t === 'str') {
|
||||
s.type = 'string'
|
||||
} else if (t === 'dict') {
|
||||
s.type = 'object'
|
||||
} else if (t === 'list') {
|
||||
s.type = 'array'
|
||||
} else if (t === 'bytes') {
|
||||
s.type = 'string'
|
||||
s.contentEncoding = 'base64'
|
||||
} else if (t === 'datetime') {
|
||||
s.type = 'string'
|
||||
s.format = 'date-time'
|
||||
} else {
|
||||
s.type = undefined
|
||||
}
|
||||
function argSigToJsonSchemaType(t: string | { resource: string } | { list: string }, s: SchemaProperty): void {
|
||||
if (t === 'int') {
|
||||
s.type = 'integer'
|
||||
} else if (t === 'float') {
|
||||
s.type = 'number'
|
||||
} else if (t === 'bool') {
|
||||
s.type = 'boolean'
|
||||
} else if (t === 'str') {
|
||||
s.type = 'string'
|
||||
} else if (t === 'dict') {
|
||||
s.type = 'object'
|
||||
} else if (t === 'bytes') {
|
||||
s.type = 'string'
|
||||
s.contentEncoding = 'base64'
|
||||
} else if (t === 'datetime') {
|
||||
s.type = 'string'
|
||||
s.format = 'date-time'
|
||||
} else if (typeof t !== 'string' && `resource` in t) {
|
||||
s.type = 'object'
|
||||
s.format = `resource-${t.resource}`
|
||||
} else if (typeof t !== 'string' && `list` in t) {
|
||||
s.type = 'array'
|
||||
if (t.list === 'int' || t.list === 'float') {
|
||||
s.items = { type: 'number' }
|
||||
} else if (t.list === 'bytes') {
|
||||
s.items = { type: 'string', contentEncoding: 'base64' }
|
||||
} else {
|
||||
s.items = { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,92 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast'
|
||||
import { onMount } from 'svelte'
|
||||
import { WorkspaceService } from '../gen'
|
||||
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '../stores'
|
||||
import {
|
||||
getUserExt,
|
||||
logout,
|
||||
logoutWithRedirect,
|
||||
refreshSuperadmin,
|
||||
sendUserToast
|
||||
} from '../utils'
|
||||
|
||||
// Default toast options
|
||||
const toastOptions = {
|
||||
duration: 4000, // duration of progress bar tween to the `next` value
|
||||
initial: 1, // initial progress bar value
|
||||
next: 0, // next progress value
|
||||
pausable: false, // pause progress bar tween on mouse hover
|
||||
dismissable: true, // allow dismiss with close button
|
||||
reversed: false, // insert new toast to bottom of stack
|
||||
intro: { x: 256 }, // toast intro fly animation settings
|
||||
theme: {} // css var overrides
|
||||
}
|
||||
|
||||
const monacoEditorUnhandledErrors = [
|
||||
'Model not found',
|
||||
'Connection is disposed.',
|
||||
'Connection got disposed.'
|
||||
]
|
||||
|
||||
async function loadUser() {
|
||||
try {
|
||||
$usersWorkspaceStore = await WorkspaceService.listUserWorkspaces()
|
||||
await refreshSuperadmin()
|
||||
|
||||
if ($workspaceStore) {
|
||||
if ($userStore) {
|
||||
console.log(`Welcome ${$userStore.email}`)
|
||||
} else if ($superadmin) {
|
||||
console.log('You are a superadmin, you can go wherever you please')
|
||||
} else {
|
||||
$userStore = await getUserExt($workspaceStore)
|
||||
throw Error('Not logged in')
|
||||
}
|
||||
} else {
|
||||
goto('/user/workspaces')
|
||||
}
|
||||
} catch {
|
||||
logoutWithRedirect($page.url.pathname)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadUser()
|
||||
|
||||
window.onunhandledrejection = (event: PromiseRejectionEvent) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (event.reason?.message) {
|
||||
const { message, body, status } = event.reason
|
||||
|
||||
// Unhandled errors from Monaco Editor don't logout the user
|
||||
if (monacoEditorUnhandledErrors.includes(message)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (status == '401') {
|
||||
sendUserToast('Logged out after a request was unauthorized', true)
|
||||
logout($page.url.pathname)
|
||||
} else {
|
||||
sendUserToast(`${message}: ${body ?? ''}`, true)
|
||||
}
|
||||
} else {
|
||||
console.log('Caught unhandled promise rejection without message', event)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<SvelteToast options={toastOptions} />
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--toastBackground: #eff6ff;
|
||||
--toastBarBackground: #eff6ff;
|
||||
--toastColor: #123456;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,146 +1,70 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
|
||||
import { OpenAPI, UserService, WorkspaceService } from '../gen';
|
||||
import { logout, clickOutside, sendUserToast, logoutWithRedirect, getUser } from '../utils';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
faScroll,
|
||||
faPlay,
|
||||
faWallet,
|
||||
faEye,
|
||||
faChevronDown,
|
||||
faChevronRight,
|
||||
faChevronLeft,
|
||||
faBookOpen,
|
||||
faCubes,
|
||||
faCalendar,
|
||||
faRobot,
|
||||
faChevronDown,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faCog,
|
||||
faUser,
|
||||
faCrown,
|
||||
faCubes,
|
||||
faEye,
|
||||
faPlay,
|
||||
faRobot,
|
||||
faScroll,
|
||||
faUser,
|
||||
faUsersCog,
|
||||
faWallet,
|
||||
faWind
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import { faDiscord, faGithub, faPython } from '@fortawesome/free-brands-svg-icons';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
superadmin,
|
||||
usernameStore,
|
||||
userStore,
|
||||
usersWorkspaceStore,
|
||||
workspaceStore
|
||||
} from '../stores';
|
||||
import { goto } from '$app/navigation';
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { onMount } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import '../app.css'
|
||||
import { OpenAPI, ScriptService } from '../gen'
|
||||
import { hubScripts, superadmin, userStore, usersWorkspaceStore, workspaceStore } from '../stores'
|
||||
import { clickOutside, logout } from '../utils'
|
||||
|
||||
OpenAPI.WITH_CREDENTIALS = true;
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
|
||||
// Default toast options
|
||||
const toastOptions = {
|
||||
duration: 4000, // duration of progress bar tween to the `next` value
|
||||
initial: 1, // initial progress bar value
|
||||
next: 0, // next progress value
|
||||
pausable: false, // pause progress bar tween on mouse hover
|
||||
dismissable: true, // allow dismiss with close button
|
||||
reversed: false, // insert new toast to bottom of stack
|
||||
intro: { x: 256 }, // toast intro fly animation settings
|
||||
theme: {} // css var overrides
|
||||
};
|
||||
|
||||
let menuOpen = false;
|
||||
let workspacePickerOpen = false;
|
||||
let isMobile = false;
|
||||
let viewportWidth = 3000;
|
||||
let isCollapsed = false;
|
||||
let menuOpen = false
|
||||
let workspacePickerOpen = false
|
||||
let isMobile = false
|
||||
let viewportWidth = 3000
|
||||
let isCollapsed = false
|
||||
|
||||
function openMenu(): void {
|
||||
menuOpen = true;
|
||||
menuOpen = true
|
||||
}
|
||||
|
||||
function handleClickOutside(event: any): void {
|
||||
if (isMobile || viewportWidth < 640) {
|
||||
isCollapsed = true;
|
||||
isCollapsed = true
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickOutsideMenu(event: any): void {
|
||||
menuOpen = false;
|
||||
menuOpen = false
|
||||
}
|
||||
function handleClickOutsideWorkspacePicker(event: any): void {
|
||||
workspacePickerOpen = false;
|
||||
workspacePickerOpen = false
|
||||
}
|
||||
|
||||
async function loadUserInfo() {
|
||||
if ($superadmin == undefined) {
|
||||
UserService.globalWhoami().then((x) => {
|
||||
if (x.super_admin) {
|
||||
superadmin.set(x.email);
|
||||
} else {
|
||||
superadmin.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$usersWorkspaceStore) {
|
||||
try {
|
||||
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
|
||||
} catch {}
|
||||
}
|
||||
if ($usersWorkspaceStore) {
|
||||
if (!$workspaceStore) {
|
||||
workspaceStore.set(localStorage.getItem('workspace')?.toString());
|
||||
}
|
||||
if ($workspaceStore && $usernameStore) {
|
||||
await getUser($workspaceStore);
|
||||
} else if ($superadmin) {
|
||||
console.log('You are a superadmin, you can go wherever you please');
|
||||
} else {
|
||||
goto('/user/workspaces');
|
||||
}
|
||||
} else {
|
||||
logoutWithRedirect($page.url.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
localStorage.setItem('workspace', $workspaceStore);
|
||||
}
|
||||
async function loadSearchData() {
|
||||
const scripts = await ScriptService.listHubScripts()
|
||||
$hubScripts = scripts.map((x) => ({
|
||||
path: `hub/${x.id}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
|
||||
summary: `${x.summary} (${x.app})`,
|
||||
approved: x.approved
|
||||
}))
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadUserInfo();
|
||||
window.onunhandledrejection = (e) => {
|
||||
if (e.reason && e.reason.message) {
|
||||
if (
|
||||
['Model not found', 'Connection is disposed.', 'Connection got disposed.'].includes(
|
||||
e.reason.message
|
||||
)
|
||||
) {
|
||||
// monaco editor promise cancelation
|
||||
console.log('caught expected error');
|
||||
} else {
|
||||
if (e.reason.status == '401') {
|
||||
sendUserToast('Logged out after a request was unauthorized', true);
|
||||
logout($page.url.pathname);
|
||||
} else {
|
||||
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`;
|
||||
sendUserToast(message, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('unexpected error ignored', e);
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
|
||||
//Mobile
|
||||
isCollapsed = isMobile;
|
||||
});
|
||||
isCollapsed = isMobile
|
||||
loadSearchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:clientWidth={viewportWidth} class="h-full max-w-screen">
|
||||
@@ -155,7 +79,7 @@
|
||||
<button
|
||||
class="w-full flex flex-row-reverse transform hover:translate-x-1 transition-transform ease-in duration-200"
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed;
|
||||
isCollapsed = !isCollapsed
|
||||
}}
|
||||
>
|
||||
<div class="pt-1 pr-3">
|
||||
@@ -181,7 +105,7 @@
|
||||
<div
|
||||
class="flex flex-row items-center w-full justify-content"
|
||||
on:click={() => {
|
||||
workspacePickerOpen = true;
|
||||
workspacePickerOpen = true
|
||||
}}
|
||||
>
|
||||
<span class:hidden={isCollapsed} class="pr-2 font-mono text-xs flex"
|
||||
@@ -203,8 +127,8 @@
|
||||
{#each $usersWorkspaceStore?.workspaces ?? [] as workspace}
|
||||
<button
|
||||
on:click={() => {
|
||||
workspaceStore.set(workspace.id);
|
||||
workspacePickerOpen = false;
|
||||
workspaceStore.set(workspace.id)
|
||||
workspacePickerOpen = false
|
||||
}}
|
||||
class="block px-4 py-2 text-xs text-gray-500 "
|
||||
role="menuitem"
|
||||
@@ -231,7 +155,7 @@
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2"
|
||||
on:click={() => {
|
||||
localStorage.removeItem('workspace');
|
||||
localStorage.removeItem('workspace')
|
||||
}}
|
||||
>
|
||||
See all workspaces & invites</a
|
||||
@@ -259,7 +183,7 @@
|
||||
<div class="mx-auto">
|
||||
<span class:hidden={isCollapsed} class="px-2 font-mono text-xs whitespace-nowrap">
|
||||
<Icon class="text-white" data={faUser} scale={0.6} />
|
||||
{$usernameStore ?? $superadmin ?? '___'}
|
||||
{$userStore?.username ?? $superadmin ?? '___'}
|
||||
{#if $userStore?.is_admin}
|
||||
<Icon class="text-white" data={faCrown} scale={0.6} />
|
||||
{/if}
|
||||
@@ -278,7 +202,7 @@
|
||||
>
|
||||
<span class="block px-4 py-2 text-sm text-gray-500">{$usersWorkspaceStore?.email}</span>
|
||||
<a
|
||||
href="/settings"
|
||||
href="/user/settings"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
@@ -405,7 +329,7 @@
|
||||
<button
|
||||
class="h-12 flex flex-row text-sm font-medium min-w-full px-5 items-center transform hover:translate-x-1 transition-transform ease-in duration-200"
|
||||
on:click={() => {
|
||||
isCollapsed = !isCollapsed;
|
||||
isCollapsed = !isCollapsed
|
||||
}}
|
||||
>
|
||||
<div class="w-full -ml-4">
|
||||
@@ -426,7 +350,6 @@
|
||||
: 'pl-44'} pr-8 flex h-full max-w-screen flex-col items-center"
|
||||
>
|
||||
<slot />
|
||||
<SvelteToast {toastOptions} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -437,10 +360,4 @@
|
||||
.menu-link {
|
||||
@apply flex flex-row h-10 transform hover:translate-x-1 transition-transform ease-in duration-200 text-gray-200 hover:text-white;
|
||||
}
|
||||
|
||||
:root {
|
||||
--toastBackground: #eff6ff;
|
||||
--toastBarBackground: #eff6ff;
|
||||
--toastColor: #123456;
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { AuditService, AuditLog, UserService } from '../gen';
|
||||
import type { ActionKind } from '../common';
|
||||
import { page } from '$app/stores';
|
||||
import { displayDate, sendUserToast } from '../utils';
|
||||
import { goto } from '$app/navigation';
|
||||
import PageHeader from './components/PageHeader.svelte';
|
||||
import { usernameStore, userStore, workspaceStore } from '../stores';
|
||||
import TableCustom from './components/TableCustom.svelte';
|
||||
import CenteredPage from './components/CenteredPage.svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faCross, faEdit, faPlay, faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons';
|
||||
import { AuditService, AuditLog, UserService } from '../gen'
|
||||
import type { ActionKind } from '../common'
|
||||
import { page } from '$app/stores'
|
||||
import { displayDate, sendUserToast } from '../utils'
|
||||
import { goto } from '$app/navigation'
|
||||
import PageHeader from './components/PageHeader.svelte'
|
||||
import { userStore, workspaceStore } from '../stores'
|
||||
import TableCustom from './components/TableCustom.svelte'
|
||||
import CenteredPage from './components/CenteredPage.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faCross, faEdit, faPlay, faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
let logs: AuditLog[];
|
||||
let usernames: string[];
|
||||
let logs: AuditLog[]
|
||||
let usernames: string[]
|
||||
|
||||
// Get all page params
|
||||
let username: string | undefined = $page.url.searchParams.get('username') ?? undefined;
|
||||
let pageIndex: number | undefined = Number($page.url.searchParams.get('page')) || undefined;
|
||||
let before: string | undefined = $page.url.searchParams.get('before') ?? undefined;
|
||||
let after: string | undefined = $page.url.searchParams.get('after') ?? undefined;
|
||||
let perPage: number | undefined = Number($page.url.searchParams.get('perPage')) || undefined;
|
||||
let operation: string | undefined = $page.url.searchParams.get('operation') ?? undefined;
|
||||
let resource: string | undefined = $page.url.searchParams.get('resource') ?? undefined;
|
||||
let username: string | undefined = $page.url.searchParams.get('username') ?? undefined
|
||||
let pageIndex: number | undefined = Number($page.url.searchParams.get('page')) || undefined
|
||||
let before: string | undefined = $page.url.searchParams.get('before') ?? undefined
|
||||
let after: string | undefined = $page.url.searchParams.get('after') ?? undefined
|
||||
let perPage: number | undefined = Number($page.url.searchParams.get('perPage')) || undefined
|
||||
let operation: string | undefined = $page.url.searchParams.get('operation') ?? undefined
|
||||
let resource: string | undefined = $page.url.searchParams.get('resource') ?? undefined
|
||||
let actionKind: ActionKind | undefined =
|
||||
($page.url.searchParams.get('actionKind') as ActionKind) ?? undefined;
|
||||
($page.url.searchParams.get('actionKind') as ActionKind) ?? undefined
|
||||
|
||||
async function loadLogs(username: string | undefined, page: number | undefined): Promise<void> {
|
||||
try {
|
||||
if (username == 'all') {
|
||||
username = undefined;
|
||||
username = undefined
|
||||
}
|
||||
logs = await AuditService.listAuditLogs({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -40,50 +40,48 @@
|
||||
operation,
|
||||
resource,
|
||||
actionKind
|
||||
});
|
||||
})
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load users: ${err}`, true);
|
||||
sendUserToast(`Could not load users: ${err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load users: ${err}`, true);
|
||||
sendUserToast(`Could not load users: ${err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
async function gotoUsername(username: string | undefined): Promise<void> {
|
||||
goto(`?username=` + (username ? encodeURIComponent(username) : ''));
|
||||
goto(`?username=` + (username ? encodeURIComponent(username) : ''))
|
||||
}
|
||||
|
||||
async function gotoPage(index: number): Promise<void> {
|
||||
pageIndex = index;
|
||||
goto(`?page=${index}` + (username ? `&username=${encodeURIComponent(username)}` : ''));
|
||||
pageIndex = index
|
||||
goto(`?page=${index}` + (username ? `&username=${encodeURIComponent(username)}` : ''))
|
||||
}
|
||||
|
||||
function kindToIcon(kind: string) {
|
||||
if (kind == 'Execute') {
|
||||
return faPlay;
|
||||
return faPlay
|
||||
} else if (kind == 'Delete') {
|
||||
return faCross;
|
||||
return faCross
|
||||
} else if (kind == 'Update') {
|
||||
return faEdit;
|
||||
return faEdit
|
||||
} else if (kind == 'Create') {
|
||||
return faPlus;
|
||||
return faPlus
|
||||
}
|
||||
return faQuestion;
|
||||
return faQuestion
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadUsers();
|
||||
loadLogs(username, pageIndex);
|
||||
}
|
||||
if ($usernameStore) {
|
||||
username = $usernameStore;
|
||||
loadUsers()
|
||||
loadLogs(username, pageIndex)
|
||||
}
|
||||
username = $userStore?.username
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -115,10 +113,10 @@
|
||||
|
||||
<TableCustom
|
||||
on:next={() => {
|
||||
gotoPage((pageIndex ?? 1) + 1);
|
||||
gotoPage((pageIndex ?? 1) + 1)
|
||||
}}
|
||||
on:previous={() => {
|
||||
gotoPage((pageIndex ?? 1) - 1);
|
||||
gotoPage((pageIndex ?? 1) - 1)
|
||||
}}
|
||||
currentPage={pageIndex}
|
||||
>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { truncate } from '../../utils';
|
||||
import Modal from './Modal.svelte';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import json from 'svelte-highlight/src/languages/json';
|
||||
import github from 'svelte-highlight/src/styles/github';
|
||||
import { Highlight } from 'svelte-highlight';
|
||||
import { ResourceService, type Resource } from '../../gen';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { truncate } from '../../utils'
|
||||
import Modal from './Modal.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import json from 'svelte-highlight/languages/json'
|
||||
import github from 'svelte-highlight/styles/github'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { ResourceService, type Resource } from '../../gen'
|
||||
import { workspaceStore } from '../../stores'
|
||||
|
||||
export let value: any;
|
||||
let resourceViewer: Modal;
|
||||
let resource: Resource;
|
||||
export let value: any
|
||||
let resourceViewer: Modal
|
||||
let resource: Resource
|
||||
|
||||
function isString(value: any) {
|
||||
return typeof value === 'string' || value instanceof String;
|
||||
return typeof value === 'string' || value instanceof String
|
||||
}
|
||||
|
||||
async function getResource(path) {
|
||||
resource = await ResourceService.getResource({ workspace: $workspaceStore!, path });
|
||||
resource = await ResourceService.getResource({ workspace: $workspaceStore!, path })
|
||||
}
|
||||
|
||||
let asJson: string = JSON.stringify(value, null, 4);
|
||||
let asJson: string = JSON.stringify(value, null, 4)
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -43,8 +43,8 @@
|
||||
<button
|
||||
class="text-xs text-blue-500"
|
||||
on:click={async () => {
|
||||
await getResource(value.substring('$res:'.length));
|
||||
resourceViewer.openModal();
|
||||
await getResource(value.substring('$res:'.length))
|
||||
resourceViewer.openModal()
|
||||
}}>{value}</button
|
||||
>{:else if asJson.length > 40}
|
||||
{truncate(asJson, 40)}<Tooltip>{asJson}</Tooltip>
|
||||
|
||||
@@ -1,115 +1,116 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
import { slide } from 'svelte/transition';
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import ResourcePicker from './ResourcePicker.svelte';
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte';
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte';
|
||||
import FieldHeader from './FieldHeader.svelte';
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
|
||||
export let label: string = '';
|
||||
export let value: any;
|
||||
export let defaultValue: any = undefined;
|
||||
export let description: string = '';
|
||||
export let format: string = '';
|
||||
export let contentEncoding = '';
|
||||
export let type: string | undefined = undefined;
|
||||
export let required = false;
|
||||
export let pattern: undefined | string;
|
||||
export let valid = required ? false : true;
|
||||
export let minRows = 1;
|
||||
export let maxRows = 10;
|
||||
export let enum_: string[] | undefined = undefined;
|
||||
export let disabled = false;
|
||||
export let editableSchema = false;
|
||||
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined;
|
||||
export let displayHeader = true;
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
export let defaultValue: any = undefined
|
||||
export let description: string = ''
|
||||
export let format: string = ''
|
||||
export let contentEncoding = ''
|
||||
export let type: string | undefined = undefined
|
||||
export let required = false
|
||||
export let pattern: undefined | string
|
||||
export let valid = required ? false : true
|
||||
export let minRows = 1
|
||||
export let maxRows = 10
|
||||
export let enum_: string[] | undefined = undefined
|
||||
export let disabled = false
|
||||
export let editableSchema = false
|
||||
export let itemsType: { type?: 'string' | 'number'; contentEncoding?: string } | undefined =
|
||||
undefined
|
||||
export let displayHeader = true
|
||||
|
||||
let seeEditable: boolean = enum_ != undefined || pattern != undefined;
|
||||
let seeEditable: boolean = enum_ != undefined || pattern != undefined
|
||||
|
||||
$: minHeight = `${1 + minRows * 1.2}em`;
|
||||
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
|
||||
$: minHeight = `${1 + minRows * 1.2}em`
|
||||
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
|
||||
|
||||
$: validateInput(pattern, value);
|
||||
$: validateInput(pattern, value)
|
||||
|
||||
let error: string = '';
|
||||
let error: string = ''
|
||||
|
||||
let rawValue: string | undefined;
|
||||
let rawValue: string | undefined
|
||||
|
||||
$: {
|
||||
if (rawValue) {
|
||||
try {
|
||||
value = JSON.parse(rawValue);
|
||||
value = JSON.parse(rawValue)
|
||||
} catch (err) {
|
||||
error = err.toString();
|
||||
error = err.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (!type || type == 'object' || (type == 'array' && itemsType?.type == undefined)) {
|
||||
evalValueToRaw();
|
||||
evalValueToRaw()
|
||||
}
|
||||
if (defaultValue) {
|
||||
let stringified = JSON.stringify(defaultValue, null, 4);
|
||||
let stringified = JSON.stringify(defaultValue, null, 4)
|
||||
if (stringified.length > 50) {
|
||||
minRows = 3;
|
||||
minRows = 3
|
||||
}
|
||||
if (type != 'string') {
|
||||
minRows = Math.max(minRows, Math.min(stringified.split(/\r\n|\r|\n/).length + 1, maxRows));
|
||||
minRows = Math.max(minRows, Math.min(stringified.split(/\r\n|\r|\n/).length + 1, maxRows))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function evalValueToRaw() {
|
||||
rawValue = JSON.stringify(value, null, 4);
|
||||
rawValue = JSON.stringify(value, null, 4)
|
||||
}
|
||||
|
||||
function fileChanged(e: any) {
|
||||
let t = e.target;
|
||||
function fileChanged(e: any, cb: (v: string | undefined) => void) {
|
||||
let t = e.target
|
||||
if (t && 'files' in t && t.files.length > 0) {
|
||||
let reader = new FileReader();
|
||||
let reader = new FileReader()
|
||||
reader.onload = (e: any) => {
|
||||
value = e.target.result.split('base64,')[1];
|
||||
};
|
||||
reader.readAsDataURL(t.files[0]);
|
||||
cb(e.target.result.split('base64,')[1])
|
||||
}
|
||||
reader.readAsDataURL(t.files[0])
|
||||
} else {
|
||||
value = undefined;
|
||||
cb(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
function validateInput(pattern: string | undefined, v: any): void {
|
||||
if (required && v == undefined) {
|
||||
error = 'This field is required';
|
||||
valid = false;
|
||||
error = 'This field is required'
|
||||
valid = false
|
||||
} else {
|
||||
if (pattern && !testRegex(pattern, v)) {
|
||||
error = `Should match ${pattern}`;
|
||||
valid = false;
|
||||
error = `Should match ${pattern}`
|
||||
valid = false
|
||||
} else {
|
||||
error = '';
|
||||
valid = true;
|
||||
error = ''
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testRegex(pattern: string, value: any): boolean {
|
||||
try {
|
||||
const regex = new RegExp(pattern);
|
||||
return regex.test(value);
|
||||
const regex = new RegExp(pattern)
|
||||
return regex.test(value)
|
||||
} catch (err) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (value == undefined) {
|
||||
value = defaultValue;
|
||||
value = defaultValue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -124,7 +125,7 @@
|
||||
<span
|
||||
class="underline"
|
||||
on:click={() => {
|
||||
seeEditable = !seeEditable;
|
||||
seeEditable = !seeEditable
|
||||
}}
|
||||
>Customize argument<Icon
|
||||
class="ml-2"
|
||||
@@ -147,6 +148,9 @@
|
||||
<option value={undefined}>No specific item type</option>
|
||||
<option value={{ type: 'string' }}> Items are strings</option>
|
||||
<option value={{ type: 'number' }}>Items are numbers</option>
|
||||
<option value={{ type: 'string', contentEncoding: 'base64' }}
|
||||
>Items are bytes</option
|
||||
>
|
||||
</select>
|
||||
{/if}
|
||||
</label>
|
||||
@@ -192,15 +196,22 @@
|
||||
<div class="flex flex-row max-w-md">
|
||||
{#if itemsType.type == 'number'}
|
||||
<input type="number" bind:value={v} />
|
||||
{:else if itemsType.type == 'string' && itemsType.contentEncoding == 'base64'}
|
||||
<input
|
||||
type="file"
|
||||
class="my-6"
|
||||
on:change={(x) => fileChanged(x, (val) => (v = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{:else}
|
||||
<input type="text" bind:value={v} />
|
||||
{/if}
|
||||
<button
|
||||
class="default-button-secondary mx-6"
|
||||
on:click={() => {
|
||||
value = value.filter((el) => el != v);
|
||||
value = value.filter((el) => el != v)
|
||||
if (value.length == 0) {
|
||||
value = undefined;
|
||||
value = undefined
|
||||
}
|
||||
}}><Icon data={faMinus} class="mb-1" /></button
|
||||
>
|
||||
@@ -210,9 +221,9 @@
|
||||
class="default-button-secondary mt-1"
|
||||
on:click={() => {
|
||||
if (value == undefined) {
|
||||
value = [];
|
||||
value = []
|
||||
}
|
||||
value = value.concat('');
|
||||
value = value.concat('')
|
||||
}}>Add item <Icon data={faPlus} class="mb-1" /></button
|
||||
><span class="ml-2">{(value ?? []).length} item(s)</span>
|
||||
{:else if type == 'object' && format?.startsWith('resource')}
|
||||
@@ -236,7 +247,12 @@
|
||||
{:else if type == 'string' && format == 'date-time'}
|
||||
<input class="inline-block" type="datetime-local" bind:value />
|
||||
{:else if type == 'string' && contentEncoding == 'base64'}
|
||||
<input type="file" class="my-6" on:change={fileChanged} multiple={false} />
|
||||
<input
|
||||
type="file"
|
||||
class="my-6"
|
||||
on:change={(x) => fileChanged(x, (val) => (value = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{:else if type == 'string' && format?.startsWith('resource')}
|
||||
<ResourcePicker
|
||||
bind:value
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let value: string;
|
||||
export let placeholder = '';
|
||||
export let minRows = 1;
|
||||
export let maxRows = 20;
|
||||
export let value: string
|
||||
export let placeholder = ''
|
||||
export let minRows = 1
|
||||
export let maxRows = 20
|
||||
|
||||
$: minHeight = `${1 + minRows * 1.2}em`;
|
||||
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
|
||||
$: minHeight = `${1 + minRows * 1.2}em`
|
||||
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
export let twBgColor = 'bg-blue-200';
|
||||
export let twTextColor = 'text-gray-700';
|
||||
export let tooltip: string | undefined = undefined;
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
export let twBgColor = 'bg-blue-200'
|
||||
export let twTextColor = 'text-gray-700'
|
||||
export let tooltip: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<span class="{twBgColor} {twTextColor} text-2xs rounded px-1 whitespace-nowrap">
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
faPlay,
|
||||
faShare,
|
||||
faTrash
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let category: 'delete' | 'list' | 'run' | 'add' | 'edit' | 'archive' | 'share';
|
||||
export let disabled: boolean = false;
|
||||
const dispatch = createEventDispatcher();
|
||||
export let category: 'delete' | 'list' | 'run' | 'add' | 'edit' | 'archive' | 'share'
|
||||
export let disabled: boolean = false
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="{$$props.class} inline-flex items-center default-button py-0 px-1 {category} default-button-secondary"
|
||||
on:click={() => {
|
||||
dispatch('click');
|
||||
dispatch('click')
|
||||
}}
|
||||
{disabled}
|
||||
>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { faSortDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import type { DropdownItem } from '../../utils';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import Dropdown from './Dropdown.svelte';
|
||||
import { faSortDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { DropdownItem } from '../../utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Dropdown from './Dropdown.svelte'
|
||||
|
||||
export let dropdownItems: DropdownItem[] = [];
|
||||
export let dropdownItems: DropdownItem[] = []
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="bg-blue-500 rounded-sm {$$props.class} inline-block py-0 my-0 ">
|
||||
<button
|
||||
class="inline pl-2 text-white text-sm py-0 my-0"
|
||||
on:click={() => {
|
||||
dispatch('clickMain');
|
||||
dispatch('clickMain')
|
||||
}}><slot name="name">Add script</slot></button
|
||||
>
|
||||
<Dropdown
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import Icon from 'svelte-awesome';
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let text: string;
|
||||
export let text: string
|
||||
|
||||
export let viewOptions = false;
|
||||
export let viewOptions = false
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="mr-6 text-sm underline text-gray-700 inline-flex items-center"
|
||||
on:click={() => {
|
||||
viewOptions = !viewOptions;
|
||||
viewOptions = !viewOptions
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { Highlight } from 'svelte-highlight';
|
||||
import github from 'svelte-highlight/src/styles/github';
|
||||
import { json } from 'svelte-highlight/src/languages';
|
||||
import TableCustom from './TableCustom.svelte';
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import github from 'svelte-highlight/styles/github'
|
||||
import { json } from 'svelte-highlight/languages'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
|
||||
export let result: any;
|
||||
export let result: any
|
||||
|
||||
let resultKind: 'json' | 'table-col' | 'table-row' | 'png' | 'file' | undefined =
|
||||
inferResultKind(result);
|
||||
inferResultKind(result)
|
||||
|
||||
function isArray(obj: any) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
return Object.prototype.toString.call(obj) === '[object Array]'
|
||||
}
|
||||
|
||||
function isRectangularArray(obj: any) {
|
||||
if (!isArray(obj) || obj.length == 0) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (
|
||||
!Object.values(obj)
|
||||
.map(isArray)
|
||||
.reduce((a, b) => a && b)
|
||||
) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
let innerSize = obj[0].length;
|
||||
let innerSize = obj[0].length
|
||||
|
||||
return Object.values(obj)
|
||||
.map((x: any) => x.length == innerSize)
|
||||
.reduce((a, b) => a && b);
|
||||
.reduce((a, b) => a && b)
|
||||
}
|
||||
|
||||
function asListOfList(obj: any): ArrayLike<ArrayLike<any>> {
|
||||
return obj as ArrayLike<ArrayLike<any>>;
|
||||
return obj as ArrayLike<ArrayLike<any>>
|
||||
}
|
||||
|
||||
function inferResultKind(result: any) {
|
||||
if (result) {
|
||||
try {
|
||||
let keys = Object.keys(result);
|
||||
let keys = Object.keys(result)
|
||||
if (keys.length == 1 && isRectangularArray(result[keys[0]])) {
|
||||
return 'table-row';
|
||||
return 'table-row'
|
||||
} else if (keys.map((k) => isArray(result[k])).reduce((a, b) => a && b)) {
|
||||
return 'table-col';
|
||||
return 'table-col'
|
||||
} else if (keys.length == 1 && keys[0] == 'png') {
|
||||
return 'png';
|
||||
return 'png'
|
||||
} else if (keys.length == 1 && keys[0] == 'file') {
|
||||
return 'file';
|
||||
return 'file'
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
return 'json';
|
||||
return 'json'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { clickOutside } from '../../utils';
|
||||
import type { DropdownItem } from '../../utils';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
|
||||
import { clickOutside } from '../../utils'
|
||||
import type { DropdownItem } from '../../utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
let open = false;
|
||||
let open = false
|
||||
|
||||
export let dropdownItems: DropdownItem[];
|
||||
export let name: string | undefined = undefined;
|
||||
export let dropdownItems: DropdownItem[]
|
||||
export let name: string | undefined = undefined
|
||||
// The dropdown is positioned versus its first relatively positioned partent
|
||||
// By default, the dropdown is positioned relative to its button
|
||||
// This can cause the dropdown to be hidden if it is in an overflow-hidden div.
|
||||
// In that case, set relative to false and control the dropdown positioning from the div
|
||||
export let relative = true;
|
||||
export let relative = true
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function openMenu() {
|
||||
open = true;
|
||||
open = true
|
||||
}
|
||||
|
||||
function handleClickOutsideMenu(event: Event) {
|
||||
open = false;
|
||||
open = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -57,9 +57,9 @@
|
||||
<button
|
||||
on:click={() => {
|
||||
if (!item.disabled) {
|
||||
open = false;
|
||||
item.action && item.action();
|
||||
dispatch('click', { item: item?.eventName });
|
||||
open = false
|
||||
item.action && item.action()
|
||||
dispatch('click', { item: item?.eventName })
|
||||
}
|
||||
}}
|
||||
class="block hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30 px-4 py-2 text-sm text-gray-700 text-left{item.separatorTop
|
||||
@@ -86,7 +86,7 @@
|
||||
href={item.href}
|
||||
on:click={() => {
|
||||
if (!item.disabled) {
|
||||
open = false;
|
||||
open = false
|
||||
}
|
||||
}}
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30"
|
||||
|
||||
@@ -1,201 +1,262 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import type monaco from 'monaco-editor';
|
||||
import { browser, mode } from '$app/env';
|
||||
import { page } from '$app/stores'
|
||||
import type monaco from 'monaco-editor'
|
||||
import { browser } from '$app/env'
|
||||
|
||||
import { listen } from '@codingame/monaco-jsonrpc';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||
import {
|
||||
RequestType,
|
||||
toSocket,
|
||||
WebSocketMessageReader,
|
||||
WebSocketMessageWriter
|
||||
} from '@codingame/monaco-jsonrpc'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
||||
import type { DocumentUri, MessageTransports } from 'monaco-languageclient'
|
||||
import * as vscode from 'vscode'
|
||||
let divEl: HTMLDivElement | null = null
|
||||
let editor: monaco.editor.IStandaloneCodeEditor
|
||||
|
||||
let divEl: HTMLDivElement | null = null;
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
let monaco;
|
||||
export let lang = 'python';
|
||||
export let code: string;
|
||||
export let readOnly = false;
|
||||
export let hash: string = (Math.random() + 1).toString(36).substring(2);
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined;
|
||||
export let formatAction: (() => void) | undefined = undefined;
|
||||
export let automaticLayout = true;
|
||||
export let websocketAlive = { pyright: false, black: false };
|
||||
let websockets: WebSocket[] = [];
|
||||
|
||||
let disposeMethod: () => void | undefined;
|
||||
export let deno = false
|
||||
export let lang = deno ? 'typescript' : 'python'
|
||||
export let code: string
|
||||
export let hash: string = (Math.random() + 1).toString(36).substring(2)
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined
|
||||
export let formatAction: (() => void) | undefined = undefined
|
||||
export let automaticLayout = true
|
||||
export let websocketAlive = { pyright: false, black: false, deno: false }
|
||||
let websockets: WebSocket[] = []
|
||||
let uri: string = ''
|
||||
let disposeMethod: () => void | undefined
|
||||
|
||||
if (browser) {
|
||||
// @ts-ignore
|
||||
self.MonacoEnvironment = {
|
||||
getWorker: function (_moduleId: any, label: string) {
|
||||
if (label === 'json') {
|
||||
return new jsonWorker();
|
||||
return new jsonWorker()
|
||||
} else if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker()
|
||||
} else {
|
||||
return new editorWorker()
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker();
|
||||
}
|
||||
return new editorWorker();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getCode(): string {
|
||||
return editor?.getValue();
|
||||
return editor?.getValue()
|
||||
}
|
||||
|
||||
export function insertAtCursor(code: string): void {
|
||||
if (editor) {
|
||||
editor.trigger('keyboard', 'type', { text: code });
|
||||
editor.trigger('keyboard', 'type', { text: code })
|
||||
}
|
||||
}
|
||||
|
||||
export function insertAtBeginning(code: string): void {
|
||||
if (editor) {
|
||||
const range = new monaco.Range(1, 1, 1, 1);
|
||||
const op = { range: range, text: code, forceMoveMarkers: true };
|
||||
editor.executeEdits('external', [op]);
|
||||
const range = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }
|
||||
const op = { range: range, text: code, forceMoveMarkers: true }
|
||||
editor.executeEdits('external', [op])
|
||||
}
|
||||
}
|
||||
|
||||
export function setCode(ncode: string): void {
|
||||
if (editor) {
|
||||
return editor.setValue(ncode);
|
||||
return editor.setValue(ncode)
|
||||
} else {
|
||||
code = ncode;
|
||||
code = ncode
|
||||
}
|
||||
}
|
||||
|
||||
function format() {
|
||||
if (editor) {
|
||||
editor.getAction('editor.action.formatDocument').run();
|
||||
editor.getAction('editor.action.formatDocument').run()
|
||||
if (formatAction) {
|
||||
formatAction();
|
||||
formatAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function reloadWebsocket() {
|
||||
closeWebsockets();
|
||||
if (lang == 'python') {
|
||||
closeWebsockets()
|
||||
if (lang == 'python' || deno) {
|
||||
// install Monaco language client services
|
||||
const { MonacoLanguageClient, CloseAction, ErrorAction, createConnection } = await import(
|
||||
'@codingame/monaco-languageclient'
|
||||
);
|
||||
const { MonacoLanguageClient } = await import('monaco-languageclient')
|
||||
const { CloseAction, ErrorAction } = await import('vscode-languageclient')
|
||||
|
||||
function createLanguageClient(connection: any, name: string, initializationOptions?: any) {
|
||||
return new MonacoLanguageClient({
|
||||
function createLanguageClient(
|
||||
transports: MessageTransports,
|
||||
name: string,
|
||||
initializationOptions?: any
|
||||
) {
|
||||
const client = new MonacoLanguageClient({
|
||||
name: name,
|
||||
clientOptions: {
|
||||
documentSelector: ['python'],
|
||||
documentSelector: deno ? ['typescript'] : ['python'],
|
||||
errorHandler: {
|
||||
error: () => ErrorAction.Shutdown,
|
||||
closed: () => CloseAction.Restart
|
||||
error: () => ({ action: ErrorAction.Shutdown }),
|
||||
closed: () => ({
|
||||
action: CloseAction.Restart
|
||||
})
|
||||
},
|
||||
markdown: {
|
||||
isTrusted: true
|
||||
},
|
||||
|
||||
// workspaceFolder: { uri: Uri.parse(`/tmp/${name}`), name: 'tmp', index: 0 },
|
||||
initializationOptions
|
||||
initializationOptions,
|
||||
middleware: {
|
||||
workspace: {
|
||||
configuration: (params, token, configuration) => {
|
||||
return [
|
||||
{
|
||||
enable: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
connectionProvider: {
|
||||
get: (errorHandler, closeHandler) => {
|
||||
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
|
||||
get: () => {
|
||||
return Promise.resolve(transports)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
return client
|
||||
}
|
||||
|
||||
function connectToLanguageServer(url: string, name: string, options?: any) {
|
||||
async function connectToLanguageServer(url: string, name: string, options?: any) {
|
||||
try {
|
||||
const webSocket = new WebSocket(url);
|
||||
websockets.push(webSocket);
|
||||
// listen when the web socket is opened
|
||||
listen({
|
||||
webSocket,
|
||||
onConnection: (connection) => {
|
||||
// create and start the language client
|
||||
const languageClient = createLanguageClient(connection, name, options);
|
||||
const disposable = languageClient.start();
|
||||
websocketAlive[name] = true;
|
||||
const webSocket = new WebSocket(url)
|
||||
|
||||
connection.onClose(() => {
|
||||
websocketAlive[name] = false;
|
||||
try {
|
||||
disposable.dispose();
|
||||
} catch (err) {
|
||||
console.error('error disposing websocket', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
webSocket.onopen = () => {
|
||||
websockets.push(webSocket)
|
||||
const socket = toSocket(webSocket)
|
||||
const reader = new WebSocketMessageReader(socket)
|
||||
const writer = new WebSocketMessageWriter(socket)
|
||||
const languageClient = createLanguageClient({ reader, writer }, name, options)
|
||||
languageClient.start()
|
||||
socket.onClose((_code, _reason) => {
|
||||
websocketAlive[name] = false
|
||||
})
|
||||
|
||||
vscode.commands.registerCommand('deno.cache', (uris: DocumentUri[] = []) => {
|
||||
languageClient.sendRequest(new RequestType('deno/cache'), {
|
||||
referrer: { uri },
|
||||
uris: uris.map((uri) => ({ uri }))
|
||||
})
|
||||
})
|
||||
|
||||
websocketAlive[name] = true
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`connection to ${name} language server failed`);
|
||||
console.error(`connection to ${name} language server failed`)
|
||||
}
|
||||
}
|
||||
connectToLanguageServer(`wss://${$page.url.host}/ws/pyright`, 'pyright', {
|
||||
executionEnvironments: [
|
||||
{
|
||||
root: '/tmp/pyright',
|
||||
pythonVersion: '3.7',
|
||||
pythonPlatform: 'platform',
|
||||
extraPaths: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
|
||||
formatters: {
|
||||
black: {
|
||||
command: 'black',
|
||||
args: ['--quiet', '-']
|
||||
if (deno) {
|
||||
await connectToLanguageServer(`wss://${$page.url.host}/ws/deno`, 'deno', {
|
||||
certificateStores: null,
|
||||
enablePaths: [],
|
||||
config: null,
|
||||
importMap: null,
|
||||
internalDebug: false,
|
||||
lint: false,
|
||||
path: null,
|
||||
tlsCertificate: null,
|
||||
unsafelyIgnoreCertificateErrors: null,
|
||||
unstable: false,
|
||||
enable: true,
|
||||
cache: null,
|
||||
codeLens: {
|
||||
implementations: true,
|
||||
references: true
|
||||
},
|
||||
suggest: {
|
||||
autoImports: true,
|
||||
completeFunctionCalls: false,
|
||||
names: true,
|
||||
paths: true,
|
||||
imports: {
|
||||
autoDiscover: true,
|
||||
hosts: {
|
||||
'https://deno.land': true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
formatFiletypes: {
|
||||
python: 'black'
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
await connectToLanguageServer(`wss://${$page.url.host}/ws/pyright`, 'pyright', {
|
||||
executionEnvironments: [
|
||||
{
|
||||
root: '/tmp/pyright',
|
||||
pythonVersion: '3.7',
|
||||
pythonPlatform: 'platform',
|
||||
extraPaths: []
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
|
||||
formatters: {
|
||||
black: {
|
||||
command: 'black',
|
||||
args: ['--quiet', '-']
|
||||
}
|
||||
},
|
||||
formatFiletypes: {
|
||||
python: 'black'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeWebsockets() {
|
||||
websockets.forEach((x) => {
|
||||
for (const x of websockets) {
|
||||
try {
|
||||
x.close();
|
||||
x.close()
|
||||
} catch (err) {
|
||||
console.log('error disposing websocket', err);
|
||||
console.log('error disposing websocket', err)
|
||||
}
|
||||
});
|
||||
}
|
||||
websockets = []
|
||||
}
|
||||
async function loadMonaco() {
|
||||
monaco = await import('monaco-editor');
|
||||
|
||||
async function loadMonaco() {
|
||||
const monaco = await import('monaco-editor')
|
||||
if (lang == 'python') {
|
||||
monaco.languages.register({
|
||||
id: 'python',
|
||||
extensions: ['.py'],
|
||||
aliases: ['python'],
|
||||
mimetypes: ['application/text']
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
let path: string = 'unknown';
|
||||
let path: string = 'unknown'
|
||||
if (lang == 'python') {
|
||||
path = `${hash}.py`;
|
||||
path = `${hash}.py`
|
||||
} else if (lang == 'json') {
|
||||
path = `${hash}.json`;
|
||||
path = `${hash}.json`
|
||||
} else if (lang == 'javascript') {
|
||||
path = `${hash}.js`;
|
||||
path = `${hash}.js`
|
||||
} else if (lang == 'typescript') {
|
||||
path = `${hash}.ts`;
|
||||
path = `${hash}.ts`
|
||||
}
|
||||
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${path}`));
|
||||
model.updateOptions({ tabSize: 4, insertSpaces: true });
|
||||
uri = `file:///${path}`
|
||||
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(uri))
|
||||
model.updateOptions({ tabSize: 4, insertSpaces: true })
|
||||
editor = monaco.editor.create(divEl as HTMLDivElement, {
|
||||
model: model,
|
||||
value: code,
|
||||
language: lang,
|
||||
automaticLayout,
|
||||
readOnly: readOnly,
|
||||
readOnly: false,
|
||||
autoDetectHighContrast: true,
|
||||
//lineNumbers: 'off',
|
||||
//lineDecorationsWidth: 0,
|
||||
@@ -206,21 +267,21 @@
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
|
||||
format();
|
||||
});
|
||||
format()
|
||||
})
|
||||
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
|
||||
if (cmdEnterAction) {
|
||||
cmdEnterAction();
|
||||
cmdEnterAction()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
editor.onDidChangeModelContent((event) => {
|
||||
code = getCode();
|
||||
});
|
||||
code = getCode()
|
||||
})
|
||||
|
||||
if (lang == 'json') {
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
@@ -228,19 +289,24 @@
|
||||
allowComments: false,
|
||||
schemas: [],
|
||||
enableSchemaRequest: true
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (lang == 'typescript') {
|
||||
// compiler options
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
target: monaco.languages.typescript.ScriptTarget.ES6,
|
||||
target: monaco.languages.typescript.ScriptTarget.Latest,
|
||||
allowNonTsExtensions: true,
|
||||
noLib: true
|
||||
});
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
`
|
||||
})
|
||||
if (deno) {
|
||||
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
||||
noSemanticValidation: true,
|
||||
noSuggestionDiagnostics: true,
|
||||
noSyntaxValidation: true
|
||||
})
|
||||
} else {
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
`
|
||||
/**
|
||||
* get variable (including secret) at path
|
||||
* @param {string} path - path of the variable (e.g: g/all/pretty_secret)
|
||||
@@ -276,40 +342,41 @@ export const previous_result: any;
|
||||
*/
|
||||
export const params: any;
|
||||
`,
|
||||
'file:///node_modules/@types/windmill/index.d.ts'
|
||||
);
|
||||
'file:///node_modules/@types/windmill/index.d.ts'
|
||||
)
|
||||
}
|
||||
}
|
||||
if (lang == 'python' || deno) {
|
||||
const { MonacoServices } = await import('monaco-languageclient')
|
||||
|
||||
MonacoServices.install(monaco)
|
||||
}
|
||||
|
||||
if (lang == 'python') {
|
||||
const { MonacoServices } = await import('@codingame/monaco-languageclient');
|
||||
|
||||
MonacoServices.install(monaco);
|
||||
}
|
||||
|
||||
reloadWebsocket();
|
||||
reloadWebsocket()
|
||||
|
||||
return () => {
|
||||
if (editor) {
|
||||
try {
|
||||
editor.dispose();
|
||||
closeWebsockets()
|
||||
editor.dispose()
|
||||
} catch (err) {
|
||||
console.log('error disposing editor', err);
|
||||
console.log('error disposing editor', err)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
loadMonaco().then((x) => (disposeMethod = x));
|
||||
loadMonaco().then((x) => (disposeMethod = x))
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (disposeMethod) {
|
||||
disposeMethod();
|
||||
disposeMethod()
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- <button class="default-button px-6 max-h-8" type="button" on:click={format}>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Required from './Required.svelte';
|
||||
import Required from './Required.svelte'
|
||||
|
||||
export let label: string;
|
||||
export let format: string = '';
|
||||
export let contentEncoding = '';
|
||||
export let type: string | undefined = undefined;
|
||||
export let required = false;
|
||||
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined;
|
||||
export let label: string
|
||||
export let format: string = ''
|
||||
export let contentEncoding = ''
|
||||
export let type: string | undefined = undefined
|
||||
export let required = false
|
||||
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined
|
||||
</script>
|
||||
|
||||
<h3>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user