Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
5
.env
5
.env
@@ -1,3 +1,6 @@
|
||||
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'
|
||||
|
||||
---
|
||||
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"
|
||||
2
.github/workflows/change-versions.yml
vendored
2
.github/workflows/change-versions.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
change_version:
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
90
.github/workflows/docker-image.yml
vendored
90
.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,66 @@ 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: |
|
||||
${{ steps.metalocal.outputs.tags }}
|
||||
|
||||
labels: ${{ steps.metalocal.outputs.labels }}
|
||||
cache-from: type=registry,ref=registry.wimill.xyz/windmilllabs/windmill:buildcache
|
||||
cache-to: type=registry,ref=registry.wimill.xyz/windmilllabs/windmill: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: |
|
||||
${{ steps.metalocal.outputs.tags }}
|
||||
${{ steps.meta.outputs.tags }}
|
||||
|
||||
labels: ${{ steps.metalocal.outputs.labels }}
|
||||
cache-from: type=registry,ref=registry.wimill.xyz/windmilllabs/windmill:buildcache
|
||||
cache-to: type=registry,ref=registry.wimill.xyz/windmilllabs/windmill:buildcache,mode=max
|
||||
|
||||
37
.github/workflows/lsp_on_release.yml
vendored
Normal file
37
.github/workflows/lsp_on_release.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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: registry.wimill.xyz/windmilllabs/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=registry.wimill.xyz/windmilllabs/lsp:buildcache
|
||||
cache-to: type=registry,ref=registry.wimill.xyz/windmilllabs/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:
|
||||
build_lsp:
|
||||
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
|
||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,3 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## [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.
|
||||
@@ -1,4 +1,4 @@
|
||||
{$SITE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /* server:8000
|
||||
reverse_proxy /* windmill:8000
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
50
README.md
50
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
|
||||
- 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
|
||||
@@ -69,18 +91,28 @@ 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 volume create caddy_data && docker-compose up` with the following
|
||||
docker-compose is sufficient:
|
||||
<https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml>
|
||||
|
||||
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.
|
||||
|
||||
## Copyright
|
||||
|
||||
|
||||
44
backend/Cargo.lock
generated
44
backend/Cargo.lock
generated
@@ -224,7 +224,7 @@ dependencies = [
|
||||
"sync_wrapper",
|
||||
"tokio 1.17.0",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-http 0.2.5",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
@@ -2695,9 +2695,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -2713,9 +2713,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.136"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2724,9 +2724,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa 1.0.1",
|
||||
@@ -3114,18 +3114,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
version = "1.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3437,6 +3437,24 @@ dependencies = [
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d342c6d58709c0a6d48d48dabbb62d4ef955cf5f0f3bbfd845838e7ae88dbae"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes 1.1.0",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body 0.4.4",
|
||||
"http-range-header",
|
||||
"pin-project-lite 0.2.8",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -4011,7 +4029,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.5.0"
|
||||
version = "1.7.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4056,7 +4074,7 @@ dependencies = [
|
||||
"tokio-util 0.7.1",
|
||||
"tower",
|
||||
"tower-cookies",
|
||||
"tower-http",
|
||||
"tower-http 0.3.3",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ulid",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.5.0"
|
||||
version = "1.7.0"
|
||||
authors = ["Ruben Fiszel <ruben@rubenfiszel.com>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
-- Add down migration script here
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.5.0
|
||||
version: 1.7.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:
|
||||
@@ -2607,6 +2673,8 @@ components:
|
||||
QueuedJob:
|
||||
type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
@@ -2672,6 +2740,8 @@ components:
|
||||
CompletedJob:
|
||||
type: object
|
||||
properties:
|
||||
workspace_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
@@ -2683,6 +2753,9 @@ components:
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
duration:
|
||||
type: integer
|
||||
success:
|
||||
@@ -2725,6 +2798,10 @@ components:
|
||||
type: boolean
|
||||
required:
|
||||
- id
|
||||
- created_by
|
||||
- duration
|
||||
- created_at
|
||||
- started_at
|
||||
- success
|
||||
- canceled
|
||||
- job_kind
|
||||
@@ -3243,7 +3320,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": {
|
||||
@@ -1249,6 +1266,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": {
|
||||
@@ -2713,69 +2781,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": {
|
||||
|
||||
@@ -92,6 +92,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>,
|
||||
@@ -433,7 +434,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",
|
||||
@@ -544,6 +545,7 @@ async fn list_completed_jobs(
|
||||
"parent_job",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"started_at",
|
||||
"duration",
|
||||
"success",
|
||||
"script_hash",
|
||||
@@ -866,6 +868,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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
- ./init-db.sql:/docker-entrypoint-initdb.d/create_tables.sql
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
@@ -18,13 +19,22 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
windmill:
|
||||
image: windmill:main
|
||||
image: ghcr.io/windmill-labs/windmill:main
|
||||
privileged: true
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
|
||||
- VARIABLES_KEY=changeme
|
||||
- APP_USER_PASSWORD=changeme
|
||||
- BASE_URL=https://${SITE_URL}
|
||||
- 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}
|
||||
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -20,4 +20,4 @@ module.exports = {
|
||||
rules: {
|
||||
'no-console': ['log', { allow: ['warn', 'error'] }]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100
|
||||
"printWidth": 100,
|
||||
"semi": false
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"dependencies": {
|
||||
"@codingame/monaco-jsonrpc": "^0.3.1",
|
||||
"@codingame/monaco-languageclient": "^0.17.0",
|
||||
@@ -362,9 +362,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/kit": {
|
||||
"version": "1.0.0-next.324",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.324.tgz",
|
||||
"integrity": "sha512-/CGW9rQpHQLBb2EcMw08yelD/C9hTsypymctUWdhryMTI8n1VWb0gkUcSHsz8n8oAAbKLXqwyHqeLATfcIMg2w==",
|
||||
"version": "1.0.0-next.326",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.326.tgz",
|
||||
"integrity": "sha512-prJqmXZ2H1wmFfnMw7wDujfbkcA8vuubuqUkpVVmXhfh2+SEzQscPTNwxoE5EJxb5sywtLWEvYx3hv1gPS4Lvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.32",
|
||||
@@ -5721,9 +5721,9 @@
|
||||
}
|
||||
},
|
||||
"@sveltejs/kit": {
|
||||
"version": "1.0.0-next.324",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.324.tgz",
|
||||
"integrity": "sha512-/CGW9rQpHQLBb2EcMw08yelD/C9hTsypymctUWdhryMTI8n1VWb0gkUcSHsz8n8oAAbKLXqwyHqeLATfcIMg2w==",
|
||||
"version": "1.0.0-next.326",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.326.tgz",
|
||||
"integrity": "sha512-prJqmXZ2H1wmFfnMw7wDujfbkcA8vuubuqUkpVVmXhfh2+SEzQscPTNwxoE5EJxb5sywtLWEvYx3hv1gPS4Lvg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.32",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.5.0",
|
||||
"version": "1.7.0",
|
||||
"scripts": {
|
||||
"dev": "svelte-kit dev",
|
||||
"build": "svelte-kit build",
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>windmill.dev</title>
|
||||
%svelte.head%
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>windmill.dev</title>
|
||||
%svelte.head%
|
||||
</head>
|
||||
|
||||
<body class="outline-none focus:outline-none">
|
||||
%svelte.body%
|
||||
</body>
|
||||
|
||||
<body class="outline-none focus:outline-none">
|
||||
%svelte.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' }
|
||||
}
|
||||
|
||||
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,61 @@
|
||||
import type { Schema, SchemaProperty } from "./common"
|
||||
import { ScriptService } from "./gen"
|
||||
import { sendUserToast } from "./utils"
|
||||
import type { Schema, SchemaProperty } from './common'
|
||||
import { ScriptService } 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 = {}
|
||||
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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
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 { OpenAPI, UserService, WorkspaceService } from '../gen'
|
||||
import {
|
||||
logout,
|
||||
clickOutside,
|
||||
sendUserToast,
|
||||
logoutWithRedirect,
|
||||
getUser,
|
||||
refreshSuperadmin
|
||||
} from '../utils'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import {
|
||||
faScroll,
|
||||
faPlay,
|
||||
@@ -22,20 +29,20 @@
|
||||
faCrown,
|
||||
faUsersCog,
|
||||
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';
|
||||
} 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 '../stores'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
OpenAPI.WITH_CREDENTIALS = true;
|
||||
OpenAPI.WITH_CREDENTIALS = true
|
||||
|
||||
// Default toast options
|
||||
const toastOptions = {
|
||||
@@ -47,71 +54,63 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
refreshSuperadmin()
|
||||
|
||||
if (!$usersWorkspaceStore) {
|
||||
try {
|
||||
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
|
||||
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
|
||||
} catch {}
|
||||
}
|
||||
if ($usersWorkspaceStore) {
|
||||
if (!$workspaceStore) {
|
||||
workspaceStore.set(localStorage.getItem('workspace')?.toString());
|
||||
workspaceStore.set(localStorage.getItem('workspace')?.toString())
|
||||
}
|
||||
if ($workspaceStore && $usernameStore) {
|
||||
await getUser($workspaceStore);
|
||||
await getUser($workspaceStore)
|
||||
} else if ($superadmin) {
|
||||
console.log('You are a superadmin, you can go wherever you please');
|
||||
console.log('You are a superadmin, you can go wherever you please')
|
||||
} else {
|
||||
goto('/user/workspaces');
|
||||
goto('/user/workspaces')
|
||||
}
|
||||
} else {
|
||||
logoutWithRedirect($page.url.pathname);
|
||||
logoutWithRedirect($page.url.pathname)
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
localStorage.setItem('workspace', $workspaceStore);
|
||||
localStorage.setItem('workspace', $workspaceStore)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadUserInfo();
|
||||
loadUserInfo()
|
||||
window.onunhandledrejection = (e) => {
|
||||
if (e.reason && e.reason.message) {
|
||||
if (
|
||||
@@ -120,27 +119,27 @@
|
||||
)
|
||||
) {
|
||||
// monaco editor promise cancelation
|
||||
console.log('caught expected error');
|
||||
console.log('caught expected error')
|
||||
} else {
|
||||
if (e.reason.status == '401') {
|
||||
sendUserToast('Logged out after a request was unauthorized', true);
|
||||
logout($page.url.pathname);
|
||||
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);
|
||||
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`
|
||||
sendUserToast(message, true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('unexpected error ignored', e);
|
||||
console.log('unexpected error ignored', e)
|
||||
}
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
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
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:clientWidth={viewportWidth} class="h-full max-w-screen">
|
||||
@@ -155,7 +154,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 +180,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 +202,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 +230,7 @@
|
||||
tabindex="-1"
|
||||
id="user-menu-item-2"
|
||||
on:click={() => {
|
||||
localStorage.removeItem('workspace');
|
||||
localStorage.removeItem('workspace')
|
||||
}}
|
||||
>
|
||||
See all workspaces & invites</a
|
||||
@@ -278,7 +277,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 +404,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">
|
||||
|
||||
@@ -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 { 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'
|
||||
|
||||
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,49 +40,49 @@
|
||||
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);
|
||||
loadUsers()
|
||||
loadLogs(username, pageIndex)
|
||||
}
|
||||
if ($usernameStore) {
|
||||
username = $usernameStore;
|
||||
username = $usernameStore
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -115,10 +115,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/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'
|
||||
|
||||
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,115 @@
|
||||
<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' } | 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;
|
||||
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]);
|
||||
value = e.target.result.split('base64,')[1]
|
||||
}
|
||||
reader.readAsDataURL(t.files[0])
|
||||
} else {
|
||||
value = undefined;
|
||||
value = 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 +124,7 @@
|
||||
<span
|
||||
class="underline"
|
||||
on:click={() => {
|
||||
seeEditable = !seeEditable;
|
||||
seeEditable = !seeEditable
|
||||
}}
|
||||
>Customize argument<Icon
|
||||
class="ml-2"
|
||||
@@ -198,9 +198,9 @@
|
||||
<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 +210,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')}
|
||||
|
||||
@@ -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/src/styles/github'
|
||||
import { json } from 'svelte-highlight/src/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,86 +1,86 @@
|
||||
<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, mode } 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 { 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'
|
||||
|
||||
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 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;
|
||||
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()
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new tsWorker();
|
||||
return new tsWorker()
|
||||
}
|
||||
return new editorWorker();
|
||||
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 = new monaco.Range(1, 1, 1, 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();
|
||||
closeWebsockets()
|
||||
if (lang == 'python') {
|
||||
// install Monaco language client services
|
||||
const { MonacoLanguageClient, CloseAction, ErrorAction, createConnection } = await import(
|
||||
'@codingame/monaco-languageclient'
|
||||
);
|
||||
)
|
||||
|
||||
function createLanguageClient(connection: any, name: string, initializationOptions?: any) {
|
||||
return new MonacoLanguageClient({
|
||||
@@ -99,37 +99,37 @@
|
||||
},
|
||||
connectionProvider: {
|
||||
get: (errorHandler, closeHandler) => {
|
||||
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
|
||||
return Promise.resolve(createConnection(connection, errorHandler, closeHandler))
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function connectToLanguageServer(url: string, name: string, options?: any) {
|
||||
try {
|
||||
const webSocket = new WebSocket(url);
|
||||
websockets.push(webSocket);
|
||||
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 languageClient = createLanguageClient(connection, name, options)
|
||||
const disposable = languageClient.start()
|
||||
websocketAlive[name] = true
|
||||
|
||||
connection.onClose(() => {
|
||||
websocketAlive[name] = false;
|
||||
websocketAlive[name] = false
|
||||
try {
|
||||
disposable.dispose();
|
||||
disposable.dispose()
|
||||
} catch (err) {
|
||||
console.error('error disposing websocket', err);
|
||||
console.error('error disposing websocket', err)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
} 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', {
|
||||
@@ -141,7 +141,7 @@
|
||||
extraPaths: []
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
|
||||
formatters: {
|
||||
@@ -153,21 +153,21 @@
|
||||
formatFiletypes: {
|
||||
python: 'black'
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function closeWebsockets() {
|
||||
websockets.forEach((x) => {
|
||||
try {
|
||||
x.close();
|
||||
x.close()
|
||||
} catch (err) {
|
||||
console.log('error disposing websocket', err);
|
||||
console.log('error disposing websocket', err)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
async function loadMonaco() {
|
||||
monaco = await import('monaco-editor');
|
||||
monaco = await import('monaco-editor')
|
||||
|
||||
if (lang == 'python') {
|
||||
monaco.languages.register({
|
||||
@@ -175,21 +175,21 @@
|
||||
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 });
|
||||
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${path}`))
|
||||
model.updateOptions({ tabSize: 4, insertSpaces: true })
|
||||
editor = monaco.editor.create(divEl as HTMLDivElement, {
|
||||
model: model,
|
||||
value: code,
|
||||
@@ -206,21 +206,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,7 +228,7 @@
|
||||
allowComments: false,
|
||||
schemas: [],
|
||||
enableSchemaRequest: true
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (lang == 'typescript') {
|
||||
@@ -237,7 +237,7 @@
|
||||
target: monaco.languages.typescript.ScriptTarget.ES6,
|
||||
allowNonTsExtensions: true,
|
||||
noLib: true
|
||||
});
|
||||
})
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
`
|
||||
@@ -277,39 +277,39 @@ export const previous_result: any;
|
||||
export const params: any;
|
||||
`,
|
||||
'file:///node_modules/@types/windmill/index.d.ts'
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
if (lang == 'python') {
|
||||
const { MonacoServices } = await import('@codingame/monaco-languageclient');
|
||||
const { MonacoServices } = await import('@codingame/monaco-languageclient')
|
||||
|
||||
MonacoServices.install(monaco);
|
||||
MonacoServices.install(monaco)
|
||||
}
|
||||
|
||||
reloadWebsocket();
|
||||
reloadWebsocket()
|
||||
|
||||
return () => {
|
||||
if (editor) {
|
||||
try {
|
||||
editor.dispose();
|
||||
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>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { type Flow, FlowService } from '../../gen';
|
||||
import { type Flow, FlowService } from '../../gen'
|
||||
|
||||
import { sendUserToast } from '../../utils';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import Path from './Path.svelte';
|
||||
import SvelteMarkdown from 'svelte-markdown';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import ScriptSchema from './ScriptSchema.svelte';
|
||||
import Required from './Required.svelte';
|
||||
import FlowEditor from './FlowEditor.svelte';
|
||||
import { sendUserToast } from '../../utils'
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import Path from './Path.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import Required from './Required.svelte'
|
||||
import FlowEditor from './FlowEditor.svelte'
|
||||
|
||||
export let flow: Flow;
|
||||
export let initialPath: string = '';
|
||||
export let flow: Flow
|
||||
export let initialPath: string = ''
|
||||
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1;
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1
|
||||
|
||||
async function saveFlow(): Promise<void> {
|
||||
try {
|
||||
@@ -28,7 +28,7 @@
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
});
|
||||
})
|
||||
} else {
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -40,16 +40,16 @@
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
sendUserToast(`Success! flow saved at ${flow.path}`);
|
||||
goto(`/flows/get/${flow.path}`);
|
||||
sendUserToast(`Success! flow saved at ${flow.path}`)
|
||||
goto(`/flows/get/${flow.path}`)
|
||||
} catch (error) {
|
||||
if (error.status === 400) {
|
||||
sendUserToast(error.body, true);
|
||||
sendUserToast(error.body, true)
|
||||
} else {
|
||||
sendUserToast(`Ooops.Something bad happened: ${error}`, true);
|
||||
console.error(error);
|
||||
sendUserToast(`Ooops.Something bad happened: ${error}`, true)
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,12 @@
|
||||
}
|
||||
|
||||
async function changeStep(step: number) {
|
||||
goto(`?step=${step}`);
|
||||
goto(`?step=${step}`)
|
||||
}
|
||||
|
||||
$: {
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(flow)));
|
||||
history.replaceState({}, '', $page.url);
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(flow)))
|
||||
history.replaceState({}, '', $page.url)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(1);
|
||||
changeStep(1)
|
||||
}}>Step 1: Metadata</button
|
||||
>
|
||||
<button
|
||||
@@ -86,7 +86,7 @@
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(2);
|
||||
changeStep(2)
|
||||
}}>Step 2: Flow</button
|
||||
>
|
||||
<button
|
||||
@@ -94,7 +94,7 @@
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(3);
|
||||
changeStep(3)
|
||||
}}>Step 3: UI customisation</button
|
||||
>
|
||||
</div>
|
||||
@@ -105,7 +105,7 @@
|
||||
(flow.path == undefined || flow.path == '' || flow.path.split('/')[2] == '')}
|
||||
class="default-button px-6 max-h-8"
|
||||
on:click={() => {
|
||||
changeStep(step + 1);
|
||||
changeStep(step + 1)
|
||||
}}>Next</button
|
||||
>
|
||||
{#if step == 2}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import type { Schema } from '../../common';
|
||||
import { emptySchema } from '../../utils';
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { Schema } from '../../common'
|
||||
import { emptySchema } from '../../utils'
|
||||
|
||||
import Icon from 'svelte-awesome';
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import { type Flow, FlowModuleValue, ScriptService } from '../../gen';
|
||||
import SchemaEditor from './SchemaEditor.svelte';
|
||||
import type SchemaForm from './SchemaForm.svelte';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import ModuleStep from './ModuleStep.svelte';
|
||||
import FlowPreview from './FlowPreview.svelte';
|
||||
import { type Flow, FlowModuleValue, ScriptService } from '../../gen'
|
||||
import SchemaEditor from './SchemaEditor.svelte'
|
||||
import type SchemaForm from './SchemaForm.svelte'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import ModuleStep from './ModuleStep.svelte'
|
||||
import FlowPreview from './FlowPreview.svelte'
|
||||
|
||||
export let flow: Flow;
|
||||
export let flow: Flow
|
||||
|
||||
let args: Record<string, any> = {};
|
||||
let schemas: Schema[] = [];
|
||||
let schemaForms: (SchemaForm | undefined)[] = [];
|
||||
let args: Record<string, any> = {}
|
||||
let schemas: Schema[] = []
|
||||
let schemaForms: (SchemaForm | undefined)[] = []
|
||||
|
||||
export async function loadSchemas() {
|
||||
await Promise.all(
|
||||
@@ -25,47 +25,47 @@
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: x.value.path ?? ''
|
||||
});
|
||||
})
|
||||
if (
|
||||
JSON.stringify(Object.keys(script.schema?.properties ?? {}).sort()) !=
|
||||
JSON.stringify(Object.keys(x.input_transform).sort())
|
||||
) {
|
||||
let it = {};
|
||||
let it = {}
|
||||
Object.keys(script.schema?.properties ?? {}).map(
|
||||
(x) =>
|
||||
(it[x] = {
|
||||
type: 'static',
|
||||
value: ''
|
||||
})
|
||||
);
|
||||
schemaForms[i]?.setArgs(it);
|
||||
)
|
||||
schemaForms[i]?.setArgs(it)
|
||||
}
|
||||
schemas[i] = script.schema ?? emptySchema();
|
||||
schemas[i] = script.schema ?? emptySchema()
|
||||
} else {
|
||||
schemaForms[i]?.setArgs({});
|
||||
schemas[i] = emptySchema();
|
||||
schemaForms[i]?.setArgs({})
|
||||
schemas[i] = emptySchema()
|
||||
}
|
||||
})
|
||||
);
|
||||
schemas = schemas;
|
||||
)
|
||||
schemas = schemas
|
||||
|
||||
if (flow.value.modules.length == 0) {
|
||||
addModule();
|
||||
addModule()
|
||||
}
|
||||
}
|
||||
|
||||
function addModule() {
|
||||
schemaForms.push(undefined);
|
||||
schemaForms.push(undefined)
|
||||
|
||||
let newModule = {
|
||||
value: { type: FlowModuleValue.type.SCRIPT, path: '' },
|
||||
input_transform: {}
|
||||
};
|
||||
flow.value.modules = flow.value.modules.concat(newModule);
|
||||
schemas.push(emptySchema());
|
||||
}
|
||||
flow.value.modules = flow.value.modules.concat(newModule)
|
||||
schemas.push(emptySchema())
|
||||
}
|
||||
|
||||
$: $workspaceStore && loadSchemas();
|
||||
$: $workspaceStore && loadSchemas()
|
||||
</script>
|
||||
|
||||
<!-- <PageHeader title="Flow" /> -->
|
||||
@@ -87,8 +87,8 @@
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.value.modules[0].value.path ?? ''
|
||||
});
|
||||
flow.schema = script.schema;
|
||||
})
|
||||
flow.schema = script.schema
|
||||
}}
|
||||
>Copy from step 1's schema
|
||||
</button>
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { sendUserToast, truncateRev } from '../../utils';
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import { sendUserToast, truncateRev } from '../../utils'
|
||||
|
||||
import Icon from 'svelte-awesome';
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import { type Flow, Job, JobService, InputTransform } from '../../gen';
|
||||
import { type Flow, Job, JobService, InputTransform } from '../../gen'
|
||||
|
||||
import { workspaceStore } from '../../stores';
|
||||
import RunForm from './RunForm.svelte';
|
||||
import FlowStatusViewer from './FlowStatusViewer.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import ChevronButton from './ChevronButton.svelte';
|
||||
import DisplayResult from './DisplayResult.svelte';
|
||||
import Tabs from './Tabs.svelte';
|
||||
import type { Schema } from '../../common';
|
||||
import { workspaceStore } from '../../stores'
|
||||
import RunForm from './RunForm.svelte'
|
||||
import FlowStatusViewer from './FlowStatusViewer.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import ChevronButton from './ChevronButton.svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import Tabs from './Tabs.svelte'
|
||||
import type { Schema } from '../../common'
|
||||
|
||||
export let i: number;
|
||||
export let flow: Flow;
|
||||
export let schemas: Schema[] = [];
|
||||
export let i: number
|
||||
export let flow: Flow
|
||||
export let schemas: Schema[] = []
|
||||
|
||||
export let args: Record<string, any> = {};
|
||||
export let args: Record<string, any> = {}
|
||||
|
||||
let stepArgs: Record<string, any> = {};
|
||||
let stepArgs: Record<string, any> = {}
|
||||
|
||||
let tab: 'upto' | 'justthis' = 'upto';
|
||||
let viewPreview = false;
|
||||
let intervalId: NodeJS.Timer;
|
||||
let tab: 'upto' | 'justthis' = 'upto'
|
||||
let viewPreview = false
|
||||
let intervalId: NodeJS.Timer
|
||||
|
||||
let uptoText =
|
||||
i == flow.value.modules.length - 1 ? 'Preview whole flow' : 'Preview up to this step';
|
||||
let job: Job | undefined;
|
||||
let jobs = [];
|
||||
let jobId: string;
|
||||
i == flow.value.modules.length - 1 ? 'Preview whole flow' : 'Preview up to this step'
|
||||
let job: Job | undefined
|
||||
let jobs = []
|
||||
let jobId: string
|
||||
|
||||
async function runPreview(args) {
|
||||
intervalId && clearInterval(intervalId);
|
||||
const newFlow = tab == 'upto' ? truncateFlow(flow) : extractStep(flow);
|
||||
intervalId && clearInterval(intervalId)
|
||||
const newFlow = tab == 'upto' ? truncateFlow(flow) : extractStep(flow)
|
||||
jobId = await JobService.runFlowPreview({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: {
|
||||
@@ -43,46 +43,46 @@
|
||||
value: newFlow.value,
|
||||
path: newFlow.path
|
||||
}
|
||||
});
|
||||
jobs = [];
|
||||
intervalId = setInterval(loadJob, 1000);
|
||||
sendUserToast(`started preview ${truncateRev(jobId, 10)}`);
|
||||
})
|
||||
jobs = []
|
||||
intervalId = setInterval(loadJob, 1000)
|
||||
sendUserToast(`started preview ${truncateRev(jobId, 10)}`)
|
||||
}
|
||||
|
||||
function truncateFlow(flow: Flow): Flow {
|
||||
const localFlow = JSON.parse(JSON.stringify(flow));
|
||||
localFlow.value.modules = flow.value.modules.slice(0, i + 1);
|
||||
return localFlow;
|
||||
const localFlow = JSON.parse(JSON.stringify(flow))
|
||||
localFlow.value.modules = flow.value.modules.slice(0, i + 1)
|
||||
return localFlow
|
||||
}
|
||||
|
||||
function extractStep(flow: Flow): Flow {
|
||||
const localFlow = JSON.parse(JSON.stringify(flow));
|
||||
localFlow.value.modules = flow.value.modules.slice(i, i + 1);
|
||||
localFlow.schema = schemas[i];
|
||||
stepArgs = {};
|
||||
const localFlow = JSON.parse(JSON.stringify(flow))
|
||||
localFlow.value.modules = flow.value.modules.slice(i, i + 1)
|
||||
localFlow.schema = schemas[i]
|
||||
stepArgs = {}
|
||||
Object.entries(flow.value.modules[i].input_transform).forEach((x) => {
|
||||
if (x[1].type == InputTransform.type.STATIC) {
|
||||
stepArgs[x[0]] = x[1].value;
|
||||
stepArgs[x[0]] = x[1].value
|
||||
}
|
||||
});
|
||||
return localFlow;
|
||||
})
|
||||
return localFlow
|
||||
}
|
||||
|
||||
async function loadJob() {
|
||||
try {
|
||||
job = await JobService.getJob({ workspace: $workspaceStore!, id: jobId });
|
||||
job = await JobService.getJob({ workspace: $workspaceStore!, id: jobId })
|
||||
if (job?.type == 'CompletedJob') {
|
||||
//only CompletedJob has success property
|
||||
clearInterval(intervalId);
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(err, true);
|
||||
sendUserToast(err, true)
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
intervalId && clearInterval(intervalId);
|
||||
});
|
||||
intervalId && clearInterval(intervalId)
|
||||
})
|
||||
</script>
|
||||
|
||||
<h2 class="mb-5 mt-2">
|
||||
@@ -90,7 +90,7 @@
|
||||
type="submit"
|
||||
class="underline text-gray-700 inline-flex items-center"
|
||||
on:click={() => {
|
||||
viewPreview = !viewPreview;
|
||||
viewPreview = !viewPreview
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { faHourglassHalf, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { truncateRev } from '../../utils';
|
||||
import { faHourglassHalf, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
import { truncateRev } from '../../utils'
|
||||
|
||||
import Icon from 'svelte-awesome';
|
||||
import { check } from 'svelte-awesome/icons';
|
||||
import Icon from 'svelte-awesome'
|
||||
import { check } from 'svelte-awesome/icons'
|
||||
|
||||
import {
|
||||
CompletedJob,
|
||||
FlowModuleValue,
|
||||
FlowStatusModule,
|
||||
JobService,
|
||||
QueuedJob
|
||||
} from '../../gen';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import DisplayResult from './DisplayResult.svelte';
|
||||
import ChevronButton from './ChevronButton.svelte';
|
||||
import JobStatus from './JobStatus.svelte';
|
||||
import { CompletedJob, FlowModuleValue, FlowStatusModule, JobService, QueuedJob } from '../../gen'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import ChevronButton from './ChevronButton.svelte'
|
||||
import JobStatus from './JobStatus.svelte'
|
||||
|
||||
export let job: QueuedJob | CompletedJob;
|
||||
export let jobs: (CompletedJob | undefined)[];
|
||||
export let job: QueuedJob | CompletedJob
|
||||
export let jobs: (CompletedJob | undefined)[]
|
||||
|
||||
function loadResults() {
|
||||
job?.flow_status?.modules?.forEach(async (x, i) => {
|
||||
@@ -26,13 +20,13 @@
|
||||
(i >= jobs.length && x.type == FlowStatusModule.type.SUCCESS) ||
|
||||
x.type == FlowStatusModule.type.FAILURE
|
||||
) {
|
||||
jobs.push(undefined);
|
||||
jobs[i] = await JobService.getCompletedJob({ workspace: $workspaceStore!, id: x.job! });
|
||||
jobs.push(undefined)
|
||||
jobs[i] = await JobService.getCompletedJob({ workspace: $workspaceStore!, id: x.job! })
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
$: $workspaceStore && job && loadResults();
|
||||
$: $workspaceStore && job && loadResults()
|
||||
</script>
|
||||
|
||||
<div class="flow-root max-w-lg w-full p-4">
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { workspaceStore } from '../../stores'
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import Modal from '../../routes/components/Modal.svelte';
|
||||
import { Group, GroupService, UserService } from '../../gen';
|
||||
import AutoComplete from 'simple-svelte-autocomplete';
|
||||
import PageHeader from './PageHeader.svelte';
|
||||
import TableCustom from './TableCustom.svelte';
|
||||
import { canWrite, getUser } from '../../utils';
|
||||
import Modal from '../../routes/components/Modal.svelte'
|
||||
import { Group, GroupService, UserService } from '../../gen'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import PageHeader from './PageHeader.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { canWrite, getUser } from '../../utils'
|
||||
|
||||
let name = '';
|
||||
let modal: Modal;
|
||||
let can_write = false;
|
||||
let name = ''
|
||||
let modal: Modal
|
||||
let can_write = false
|
||||
|
||||
let group: Group | undefined;
|
||||
let members: { name: string; isAdmin: boolean }[] = [];
|
||||
let usernames: string[] = [];
|
||||
let username: string = '';
|
||||
let group: Group | undefined
|
||||
let members: { name: string; isAdmin: boolean }[] = []
|
||||
let usernames: string[] = []
|
||||
let username: string = ''
|
||||
|
||||
async function loadUsernames(): Promise<void> {
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
$: {
|
||||
@@ -29,15 +29,15 @@
|
||||
return {
|
||||
name: x,
|
||||
isAdmin: x in (group?.extra_perms ?? {}) && (group?.extra_perms ?? {})[name]
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
export function openModal(newName: string): void {
|
||||
name = newName;
|
||||
loadGroup();
|
||||
loadUsernames();
|
||||
modal.openModal();
|
||||
name = newName
|
||||
loadGroup()
|
||||
loadUsernames()
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
async function addToGroup() {
|
||||
@@ -45,14 +45,14 @@
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: { username }
|
||||
});
|
||||
loadGroup();
|
||||
})
|
||||
loadGroup()
|
||||
}
|
||||
|
||||
async function loadGroup(): Promise<void> {
|
||||
group = await GroupService.getGroup({ workspace: $workspaceStore!, name });
|
||||
const user = await getUser($workspaceStore!);
|
||||
can_write = canWrite(group.name!, group.extra_perms ?? {}, user);
|
||||
group = await GroupService.getGroup({ workspace: $workspaceStore!, name })
|
||||
const user = await getUser($workspaceStore!)
|
||||
can_write = canWrite(group.name!, group.extra_perms ?? {}, user)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
workspace: $workspaceStore ?? '',
|
||||
name: group?.name ?? '',
|
||||
requestBody: { username: name }
|
||||
});
|
||||
loadGroup();
|
||||
})
|
||||
loadGroup()
|
||||
}}>remove</button
|
||||
>
|
||||
{/if}</td
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Mysql from './icons/Mysql.svelte';
|
||||
import Mail from './icons/Mail.svelte';
|
||||
import DbIcon from './icons/DbIcon.svelte';
|
||||
import PostgresIcon from './icons/PostgresIcon.svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faSlack } from '@fortawesome/free-brands-svg-icons';
|
||||
import Slack from './icons/Slack.svelte';
|
||||
import Mysql from './icons/Mysql.svelte'
|
||||
import Mail from './icons/Mail.svelte'
|
||||
import DbIcon from './icons/DbIcon.svelte'
|
||||
import PostgresIcon from './icons/PostgresIcon.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faSlack } from '@fortawesome/free-brands-svg-icons'
|
||||
import Slack from './icons/Slack.svelte'
|
||||
|
||||
export let name: string;
|
||||
export let after: boolean = false;
|
||||
export let height = '24px';
|
||||
export let width = '24px';
|
||||
export let name: string
|
||||
export let after: boolean = false
|
||||
export let height = '24px'
|
||||
export let width = '24px'
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row gap-2">
|
||||
|
||||
66
frontend/src/routes/components/InviteGlobalUser.svelte
Normal file
66
frontend/src/routes/components/InviteGlobalUser.svelte
Normal file
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '../../utils'
|
||||
import Switch from '../components/Switch.svelte'
|
||||
|
||||
import type Modal from './Modal.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { UserService } from '../../gen'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let valid = true
|
||||
|
||||
let modal: Modal
|
||||
|
||||
export function openModal(): void {
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
let email: string
|
||||
let is_super_admin = false
|
||||
let password: string
|
||||
let name: string | undefined
|
||||
let company: string | undefined
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key || event.keyCode
|
||||
if (key === 13 || key === 'Enter') {
|
||||
event.preventDefault()
|
||||
addUser()
|
||||
}
|
||||
}
|
||||
|
||||
async function addUser() {
|
||||
await UserService.createUserGlobally({
|
||||
requestBody: {
|
||||
email,
|
||||
password,
|
||||
super_admin: is_super_admin,
|
||||
name,
|
||||
company
|
||||
}
|
||||
})
|
||||
sendUserToast(`Successfully added ${email}. Welcome to them!`)
|
||||
dispatch('new')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<input on:keyup={handleKeyUp} placeholder="email" bind:value={email} />
|
||||
|
||||
<Switch class="ml-2" bind:checked={is_super_admin} horizontal={true} label={'admin: '} />
|
||||
<input on:keyup={handleKeyUp} type="password" placeholder="" bind:value={password} />
|
||||
<input on:keyup={handleKeyUp} placeholder="name" bind:value={name} />
|
||||
<input on:keyup={handleKeyUp} placeholder="company" bind:value={company} />
|
||||
|
||||
<button
|
||||
class="ml-4 w-40 {valid ? 'default-button' : 'default-button-disabled'}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
addUser()
|
||||
}}
|
||||
disabled={email == undefined || password == undefined}
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,30 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '../../utils';
|
||||
import Switch from '../components/Switch.svelte';
|
||||
import { sendUserToast } from '../../utils'
|
||||
import Switch from '../components/Switch.svelte'
|
||||
|
||||
import Modal from './Modal.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { WorkspaceService } from '../../gen';
|
||||
import type Modal from './Modal.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import { WorkspaceService } from '../../gen'
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let valid = true;
|
||||
let valid = true
|
||||
|
||||
let modal: Modal;
|
||||
let modal: Modal
|
||||
|
||||
export function openModal(): void {
|
||||
modal.openModal();
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
let email: string;
|
||||
let is_admin = false;
|
||||
let email: string
|
||||
let is_admin = false
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key || event.keyCode;
|
||||
const key = event.key || event.keyCode
|
||||
if (key === 13 || key === 'Enter') {
|
||||
event.preventDefault();
|
||||
inviteUser();
|
||||
event.preventDefault()
|
||||
inviteUser()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
email,
|
||||
is_admin
|
||||
}
|
||||
});
|
||||
sendUserToast(`Successfully invited ${email}. Welcome to them!`);
|
||||
dispatch('new');
|
||||
})
|
||||
sendUserToast(`Successfully invited ${email}. Welcome to them!`)
|
||||
dispatch('new')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
class="ml-4 w-40 {valid ? 'default-button' : 'default-button-disabled'}"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
inviteUser();
|
||||
inviteUser()
|
||||
}}
|
||||
disabled={email == undefined}
|
||||
>
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
<script lang="ts">
|
||||
import Fuse from 'fuse.js';
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
import Modal from './Modal.svelte';
|
||||
import Modal from './Modal.svelte'
|
||||
|
||||
type Item = Record<string, any>;
|
||||
export let pickCallback: (path: string, f: string) => void;
|
||||
export let loadItems: () => Promise<Item[]>;
|
||||
export let extraField: string;
|
||||
export let itemName: string;
|
||||
export let closeOnClick = true;
|
||||
type Item = Record<string, any>
|
||||
export let pickCallback: (path: string, f: string) => void
|
||||
export let loadItems: () => Promise<Item[]>
|
||||
export let extraField: string
|
||||
export let itemName: string
|
||||
export let closeOnClick = true
|
||||
|
||||
let items: Item[] = [];
|
||||
let filteredItems: Item[] = [];
|
||||
let itemsFilter = '';
|
||||
let items: Item[] = []
|
||||
let filteredItems: Item[] = []
|
||||
let itemsFilter = ''
|
||||
|
||||
const fuseOptions = {
|
||||
includeScore: false,
|
||||
keys: ['path', extraField]
|
||||
};
|
||||
const fuse: Fuse<Item> = new Fuse(items, fuseOptions);
|
||||
}
|
||||
const fuse: Fuse<Item> = new Fuse(items, fuseOptions)
|
||||
|
||||
export function openModal() {
|
||||
loadItems().then((v) => {
|
||||
items = v;
|
||||
fuse.setCollection(items);
|
||||
});
|
||||
modal.openModal();
|
||||
items = v
|
||||
fuse.setCollection(items)
|
||||
})
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
$: filteredItems =
|
||||
itemsFilter.length > 0 ? fuse.search(itemsFilter).map((value) => value.item) : items;
|
||||
itemsFilter.length > 0 ? fuse.search(itemsFilter).map((value) => value.item) : items
|
||||
|
||||
let modal: Modal;
|
||||
let modal: Modal
|
||||
</script>
|
||||
|
||||
<Modal bind:this={modal} z="z-30">
|
||||
@@ -46,9 +46,9 @@
|
||||
class="py-4 px-1 gap-1 flex flex-col hover:bg-white hover:border text-black cursor-pointer"
|
||||
on:click={() => {
|
||||
if (closeOnClick) {
|
||||
modal.closeModal();
|
||||
modal.closeModal()
|
||||
}
|
||||
pickCallback(obj['path'], obj[extraField]);
|
||||
pickCallback(obj['path'], obj[extraField])
|
||||
}}
|
||||
>
|
||||
<p class="text-sm font-semibold">
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
faClock,
|
||||
faHourglassHalf,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { displayDate, forLater } from '../../utils';
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { displayDate, forLater } from '../../utils'
|
||||
|
||||
import Icon from 'svelte-awesome';
|
||||
import { check } from 'svelte-awesome/icons';
|
||||
import Icon from 'svelte-awesome'
|
||||
import { check } from 'svelte-awesome/icons'
|
||||
|
||||
import type { CompletedJob, QueuedJob } from '../../gen';
|
||||
import type { CompletedJob, QueuedJob } from '../../gen'
|
||||
|
||||
const SMALL_ICON_SCALE = 0.7;
|
||||
const SMALL_ICON_SCALE = 0.7
|
||||
|
||||
export let job: QueuedJob | CompletedJob | undefined;
|
||||
export let job: QueuedJob | CompletedJob | undefined
|
||||
</script>
|
||||
|
||||
{#if job && 'success' in job && job.success}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let open: boolean = false;
|
||||
export let z = 'z-30';
|
||||
export let open: boolean = false
|
||||
export let z = 'z-30'
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export function closeModal(): void {
|
||||
open = false;
|
||||
dispatch('close');
|
||||
open = false
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
export function openModal(): void {
|
||||
open = true;
|
||||
dispatch('open');
|
||||
open = true
|
||||
dispatch('open')
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent): void {
|
||||
const key = event.key || event.keyCode;
|
||||
const key = event.key || event.keyCode
|
||||
if (key === 27 || key === 'Escape' || key === 'Esc') {
|
||||
if (open) {
|
||||
event.preventDefault();
|
||||
closeModal();
|
||||
event.preventDefault()
|
||||
closeModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,8 @@
|
||||
<div class="flex flex-row justify-between p-2 bg-white border-b border-gray-200">
|
||||
<button
|
||||
on:click={() => {
|
||||
open = false;
|
||||
closeModal();
|
||||
open = false
|
||||
closeModal()
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
@@ -68,7 +68,7 @@
|
||||
<div class="flex flex-row justify-between p-2 ">
|
||||
<button
|
||||
on:click={() => {
|
||||
closeModal();
|
||||
closeModal()
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { workspaceStore } from '../../stores'
|
||||
|
||||
import type { Schema } from '../../common';
|
||||
import { ScriptService, type Flow, type FlowModule } from '../../gen';
|
||||
import type { Schema } from '../../common'
|
||||
import { ScriptService, type Flow, type FlowModule } from '../../gen'
|
||||
|
||||
import SchemaForm from './SchemaForm.svelte';
|
||||
import ScriptPicker from './ScriptPicker.svelte';
|
||||
import { emptySchema } from '../../utils';
|
||||
import FlowPreview from './FlowPreview.svelte';
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import ScriptPicker from './ScriptPicker.svelte'
|
||||
import { emptySchema } from '../../utils'
|
||||
import FlowPreview from './FlowPreview.svelte'
|
||||
|
||||
export let flow: Flow;
|
||||
export let i: number;
|
||||
export let mod: FlowModule;
|
||||
export let args: Record<string, any> = {};
|
||||
export let flow: Flow
|
||||
export let i: number
|
||||
export let mod: FlowModule
|
||||
export let args: Record<string, any> = {}
|
||||
|
||||
export let schemas: Schema[] = [];
|
||||
export let schemaForms: (SchemaForm | undefined)[] = [];
|
||||
export let schemas: Schema[] = []
|
||||
export let schemaForms: (SchemaForm | undefined)[] = []
|
||||
|
||||
export async function loadSchema() {
|
||||
if (mod.value.path) {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: mod.value.path ?? ''
|
||||
});
|
||||
})
|
||||
if (
|
||||
JSON.stringify(Object.keys(script.schema?.properties ?? {}).sort()) !=
|
||||
JSON.stringify(Object.keys(mod.input_transform).sort())
|
||||
) {
|
||||
let it = {};
|
||||
let it = {}
|
||||
Object.keys(script.schema?.properties ?? {}).map(
|
||||
(x) =>
|
||||
(it[x] = {
|
||||
type: 'static',
|
||||
value: ''
|
||||
})
|
||||
);
|
||||
schemaForms[i]?.setArgs(it);
|
||||
)
|
||||
schemaForms[i]?.setArgs(it)
|
||||
}
|
||||
schemas[i] = script.schema ?? emptySchema();
|
||||
schemas[i] = script.schema ?? emptySchema()
|
||||
} else {
|
||||
schemaForms[i]?.setArgs({});
|
||||
schemas[i] = emptySchema();
|
||||
schemaForms[i]?.setArgs({})
|
||||
schemas[i] = emptySchema()
|
||||
}
|
||||
|
||||
schemas = schemas;
|
||||
schemas = schemas
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -56,10 +56,10 @@
|
||||
<button
|
||||
class="text-xs default-button-secondary max-h-6 place-self-end"
|
||||
on:click={() => {
|
||||
flow.value.modules.splice(i, 1);
|
||||
schemas.splice(i, 1);
|
||||
schemaForms.splice(i, 1);
|
||||
flow = flow;
|
||||
flow.value.modules.splice(i, 1)
|
||||
schemas.splice(i, 1)
|
||||
schemaForms.splice(i, 1)
|
||||
flow = flow
|
||||
}}
|
||||
>Remove this step
|
||||
</button>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script>
|
||||
// @ts-nocheck
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
export let id = '';
|
||||
export let value = [];
|
||||
export let readonly = false;
|
||||
export let placeholder = '';
|
||||
import { onMount } from 'svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
export let id = ''
|
||||
export let value = []
|
||||
export let readonly = false
|
||||
export let placeholder = ''
|
||||
|
||||
let input,
|
||||
inputValue,
|
||||
@@ -14,51 +14,51 @@
|
||||
showOptions = false,
|
||||
selected = {},
|
||||
first = true,
|
||||
slot;
|
||||
slot
|
||||
const iconClearPath =
|
||||
'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z';
|
||||
'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'
|
||||
|
||||
onMount(() => {
|
||||
slot.querySelectorAll('option').forEach((o) => {
|
||||
o.selected && !value.includes(o.value) && (value = [...value, o.value]);
|
||||
options = [...options, { value: o.value, name: o.textContent }];
|
||||
});
|
||||
o.selected && !value.includes(o.value) && (value = [...value, o.value])
|
||||
options = [...options, { value: o.value, name: o.textContent }]
|
||||
})
|
||||
value &&
|
||||
(selected = options.reduce(
|
||||
(obj, op) => (value.includes(op.value) ? { ...obj, [op.value]: op } : obj),
|
||||
{}
|
||||
));
|
||||
first = false;
|
||||
});
|
||||
))
|
||||
first = false
|
||||
})
|
||||
|
||||
$: if (!first) value = Object.values(selected).map((o) => o.value);
|
||||
$: if (!first) value = Object.values(selected).map((o) => o.value)
|
||||
$: filtered = options.filter((o) =>
|
||||
inputValue ? o.name.toLowerCase().includes(inputValue.toLowerCase()) : o
|
||||
);
|
||||
)
|
||||
$: if ((activeOption && !filtered.includes(activeOption)) || (!activeOption && inputValue))
|
||||
activeOption = filtered[0];
|
||||
activeOption = filtered[0]
|
||||
|
||||
function add(token) {
|
||||
if (!readonly) selected[token.value] = token;
|
||||
if (!readonly) selected[token.value] = token
|
||||
}
|
||||
|
||||
function remove(value) {
|
||||
if (!readonly) {
|
||||
const { [value]: val, ...rest } = selected;
|
||||
selected = rest;
|
||||
const { [value]: val, ...rest } = selected
|
||||
selected = rest
|
||||
}
|
||||
}
|
||||
|
||||
function optionsVisibility(show) {
|
||||
if (readonly) return;
|
||||
if (readonly) return
|
||||
if (typeof show === 'boolean') {
|
||||
showOptions = show;
|
||||
show && input.focus();
|
||||
showOptions = show
|
||||
show && input.focus()
|
||||
} else {
|
||||
showOptions = !showOptions;
|
||||
showOptions = !showOptions
|
||||
}
|
||||
if (!showOptions) {
|
||||
activeOption = undefined;
|
||||
activeOption = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,45 +66,45 @@
|
||||
if (e.keyCode === 13) {
|
||||
Object.keys(selected).includes(activeOption.value)
|
||||
? remove(activeOption.value)
|
||||
: add(activeOption);
|
||||
inputValue = '';
|
||||
: add(activeOption)
|
||||
inputValue = ''
|
||||
}
|
||||
if ([38, 40].includes(e.keyCode)) {
|
||||
// up and down arrows
|
||||
const increment = e.keyCode === 38 ? -1 : 1;
|
||||
const calcIndex = filtered.indexOf(activeOption) + increment;
|
||||
const increment = e.keyCode === 38 ? -1 : 1
|
||||
const calcIndex = filtered.indexOf(activeOption) + increment
|
||||
activeOption =
|
||||
calcIndex < 0
|
||||
? filtered[filtered.length - 1]
|
||||
: calcIndex === filtered.length
|
||||
? filtered[0]
|
||||
: filtered[calcIndex];
|
||||
: filtered[calcIndex]
|
||||
}
|
||||
}
|
||||
|
||||
function handleBlur(e) {
|
||||
optionsVisibility(false);
|
||||
optionsVisibility(false)
|
||||
}
|
||||
|
||||
function handleTokenClick(e) {
|
||||
if (e.target.closest('.token-remove')) {
|
||||
e.stopPropagation();
|
||||
remove(e.target.closest('.token').dataset.id);
|
||||
e.stopPropagation()
|
||||
remove(e.target.closest('.token').dataset.id)
|
||||
} else if (e.target.closest('.remove-all')) {
|
||||
selected = [];
|
||||
inputValue = '';
|
||||
selected = []
|
||||
inputValue = ''
|
||||
} else {
|
||||
optionsVisibility(true);
|
||||
optionsVisibility(true)
|
||||
}
|
||||
}
|
||||
|
||||
function handleOptionMousedown(e) {
|
||||
const value = e.target.dataset.value;
|
||||
const value = e.target.dataset.value
|
||||
if (selected[value]) {
|
||||
remove(value);
|
||||
remove(value)
|
||||
} else {
|
||||
add(options.filter((o) => o.value === value)[0]);
|
||||
input.focus();
|
||||
add(options.filter((o) => o.value === value)[0])
|
||||
input.focus()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService } from '../../gen';
|
||||
import { ResourceService } from '../../gen'
|
||||
|
||||
import ResourcePicker from './ResourcePicker.svelte';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import SchemaForm from './SchemaForm.svelte';
|
||||
import RadioButton from './RadioButton.svelte';
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
|
||||
export let format: string;
|
||||
export let value: any;
|
||||
export let format: string
|
||||
export let value: any
|
||||
|
||||
function isString(value: any) {
|
||||
return typeof value === 'string' || value instanceof String;
|
||||
return typeof value === 'string' || value instanceof String
|
||||
}
|
||||
|
||||
let path: string =
|
||||
isString(value) && value.length >= '$res:'.length ? value.substr('$res:'.length) : undefined;
|
||||
let args: Record<string, any> = {};
|
||||
isString(value) && value.length >= '$res:'.length ? value.substr('$res:'.length) : undefined
|
||||
let args: Record<string, any> = {}
|
||||
|
||||
if (!isString(value) && value) {
|
||||
console.log(value);
|
||||
args = value;
|
||||
console.log(value)
|
||||
args = value
|
||||
}
|
||||
|
||||
let schema: any | undefined = undefined;
|
||||
let isValid = true;
|
||||
let resourceTypeName: string = '';
|
||||
let schema: any | undefined = undefined
|
||||
let isValid = true
|
||||
let resourceTypeName: string = ''
|
||||
|
||||
async function loadSchema(format: string) {
|
||||
resourceTypeName = format.substring('resource-'.length);
|
||||
resourceTypeName = format.substring('resource-'.length)
|
||||
schema = (
|
||||
await ResourceService.getResourceType({ workspace: $workspaceStore!, path: resourceTypeName })
|
||||
).schema;
|
||||
).schema
|
||||
}
|
||||
|
||||
let option: 'resource' | 'raw' = isString(value) || value == undefined ? 'resource' : 'raw';
|
||||
let option: 'resource' | 'raw' = isString(value) || value == undefined ? 'resource' : 'raw'
|
||||
|
||||
$: {
|
||||
if (option == 'resource') {
|
||||
value = `$res:${path}`;
|
||||
value = `$res:${path}`
|
||||
} else {
|
||||
value = args;
|
||||
value = args
|
||||
}
|
||||
}
|
||||
$: format.startsWith('resource-') && loadSchema(format);
|
||||
$: format.startsWith('resource-') && loadSchema(format)
|
||||
</script>
|
||||
|
||||
<div class="max-w-lg">
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import RadioButton from './RadioButton.svelte';
|
||||
import ResourceTypePicker from './ResourceTypePicker.svelte';
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import ResourceTypePicker from './ResourceTypePicker.svelte'
|
||||
|
||||
export let format: string | undefined;
|
||||
export let format: string | undefined
|
||||
|
||||
let kind: 'resource' | 'none' = format?.startsWith('resource') ? 'resource' : 'none';
|
||||
let kind: 'resource' | 'none' = format?.startsWith('resource') ? 'resource' : 'none'
|
||||
|
||||
let resource: string | undefined = format?.startsWith('resource-')
|
||||
? format.substring('resource-'.length)
|
||||
: undefined;
|
||||
: undefined
|
||||
|
||||
$: format =
|
||||
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined;
|
||||
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined
|
||||
</script>
|
||||
|
||||
<RadioButton
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let title: string;
|
||||
export let tooltip: string = '';
|
||||
export let primary: boolean = true;
|
||||
export let title: string
|
||||
export let tooltip: string = ''
|
||||
export let primary: boolean = true
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-between mt-4 mb-2">
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { onMount } from 'svelte';
|
||||
export let password: string;
|
||||
export let label = 'password';
|
||||
export let placeholder = '******';
|
||||
import { onMount } from 'svelte'
|
||||
export let password: string
|
||||
export let label = 'password'
|
||||
export let placeholder = '******'
|
||||
|
||||
onMount(() => {
|
||||
const passwordToggle = document.querySelector('.js-password-toggle');
|
||||
const passwordToggle = document.querySelector('.js-password-toggle')
|
||||
if (passwordToggle) {
|
||||
passwordToggle.addEventListener('change', function () {
|
||||
const password = document.querySelector('.js-password'),
|
||||
passwordLabel = document.querySelector('.js-password-label');
|
||||
passwordLabel = document.querySelector('.js-password-label')
|
||||
|
||||
if (password.type === 'password') {
|
||||
password.type = 'text';
|
||||
passwordLabel.innerHTML = 'hide';
|
||||
password.type = 'text'
|
||||
passwordLabel.innerHTML = 'hide'
|
||||
} else {
|
||||
password.type = 'password';
|
||||
passwordLabel.innerHTML = 'show';
|
||||
password.type = 'password'
|
||||
passwordLabel.innerHTML = 'show'
|
||||
}
|
||||
|
||||
password.focus();
|
||||
});
|
||||
password.focus()
|
||||
})
|
||||
} else {
|
||||
throw Error('Password component is undefined');
|
||||
throw Error('Password component is undefined')
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<label class="block text-gray-700" for="password"> {label} </label>
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { type Meta, pathToMeta } from '../../common';
|
||||
import { type Meta, pathToMeta } from '../../common'
|
||||
|
||||
import type { Group } from '../../gen';
|
||||
import { GroupService } from '../../gen';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import { userStore, workspaceStore } from '../../stores';
|
||||
import { sleep } from '../../utils';
|
||||
import type { Group } from '../../gen'
|
||||
import { GroupService } from '../../gen'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { userStore, workspaceStore } from '../../stores'
|
||||
import { sleep } from '../../utils'
|
||||
|
||||
export let meta: Meta = {
|
||||
ownerKind: 'user',
|
||||
owner: '',
|
||||
name: ''
|
||||
};
|
||||
export let namePlaceholder = '';
|
||||
export let initialPath: string;
|
||||
export let path = '';
|
||||
}
|
||||
export let namePlaceholder = ''
|
||||
export let initialPath: string
|
||||
export let path = ''
|
||||
|
||||
let groups: Group[] = [];
|
||||
let error = '';
|
||||
let groups: Group[] = []
|
||||
let error = ''
|
||||
|
||||
$: {
|
||||
path = [meta.ownerKind === 'group' ? 'g' : 'u', meta.owner, meta.name].join('/');
|
||||
path = [meta.ownerKind === 'group' ? 'g' : 'u', meta.owner, meta.name].join('/')
|
||||
}
|
||||
|
||||
export function getPath() {
|
||||
return path;
|
||||
return path
|
||||
}
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta.ownerKind = 'user';
|
||||
meta.ownerKind = 'user'
|
||||
|
||||
while ($userStore == undefined) {
|
||||
await sleep(500);
|
||||
await sleep(500)
|
||||
}
|
||||
meta.owner = $userStore!.username;
|
||||
meta.name = '';
|
||||
meta.owner = $userStore!.username
|
||||
meta.name = ''
|
||||
} else {
|
||||
meta = pathToMeta(path);
|
||||
meta = pathToMeta(path)
|
||||
}
|
||||
}
|
||||
|
||||
$: validateName(meta);
|
||||
$: validateName(meta)
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
groups = await GroupService.listGroups({ workspace: $workspaceStore! });
|
||||
groups = await GroupService.listGroups({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
function validateName(meta: Meta): void {
|
||||
if (meta.name == undefined || meta.name == '') {
|
||||
error = 'choose a name';
|
||||
return;
|
||||
error = 'choose a name'
|
||||
return
|
||||
}
|
||||
const regex = new RegExp(/^[\w-]+(\/[\w-]+)*$/);
|
||||
const regex = new RegExp(/^[\w-]+(\/[\w-]+)*$/)
|
||||
if (regex.test(meta.name)) {
|
||||
error = '';
|
||||
error = ''
|
||||
} else {
|
||||
error = 'This name is not valid. ';
|
||||
error = 'This name is not valid. '
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadGroups();
|
||||
loadGroups()
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (initialPath == undefined || initialPath == '') {
|
||||
reset();
|
||||
reset()
|
||||
} else {
|
||||
meta = pathToMeta(initialPath);
|
||||
meta = pathToMeta(initialPath)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -87,9 +87,9 @@
|
||||
bind:value={meta.ownerKind}
|
||||
on:change={() => {
|
||||
if (meta.ownerKind === 'group') {
|
||||
meta.owner = 'all';
|
||||
meta.owner = 'all'
|
||||
} else {
|
||||
meta.owner = $userStore?.username ?? '';
|
||||
meta.owner = $userStore?.username ?? ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
export let label = '';
|
||||
export let options: [string, any][];
|
||||
export let value: any;
|
||||
export let small = false;
|
||||
export let label = ''
|
||||
export let options: [string, any][]
|
||||
export let value: any
|
||||
export let small = false
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<fieldset class="mt-2 mr-4">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let required: boolean;
|
||||
export let detail = '';
|
||||
export let required: boolean
|
||||
export let detail = ''
|
||||
</script>
|
||||
|
||||
{#if required}
|
||||
|
||||
@@ -4,67 +4,67 @@
|
||||
ResourceService,
|
||||
type ResourceType,
|
||||
VariableService
|
||||
} from '../../../src/gen';
|
||||
import { allTrue, emptySchema, sendUserToast } from '../../../src/utils';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { Schema } from '../../common';
|
||||
import Modal from './Modal.svelte';
|
||||
import Path from './Path.svelte';
|
||||
import ArgInput from './ArgInput.svelte';
|
||||
import AutosizedTextarea from './AutosizedTextarea.svelte';
|
||||
import ItemPicker from './ItemPicker.svelte';
|
||||
import VariableEditor from './VariableEditor.svelte';
|
||||
import Required from './Required.svelte';
|
||||
} from '../../../src/gen'
|
||||
import { allTrue, emptySchema, sendUserToast } from '../../../src/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Schema } from '../../common'
|
||||
import Modal from './Modal.svelte'
|
||||
import Path from './Path.svelte'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import AutosizedTextarea from './AutosizedTextarea.svelte'
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
import Required from './Required.svelte'
|
||||
|
||||
import { workspaceStore } from '../../stores';
|
||||
import ResourceTypePicker from './ResourceTypePicker.svelte';
|
||||
import { workspaceStore } from '../../stores'
|
||||
import ResourceTypePicker from './ResourceTypePicker.svelte'
|
||||
|
||||
let path = '';
|
||||
let initialPath = '';
|
||||
let path = ''
|
||||
let initialPath = ''
|
||||
|
||||
let step = 1;
|
||||
let step = 1
|
||||
|
||||
let resourceToEdit: Resource | undefined;
|
||||
let resourceToEdit: Resource | undefined
|
||||
|
||||
let description: string = '';
|
||||
let description: string = ''
|
||||
let DESCRIPTION_PLACEHOLDER = `You can use markdown to style your description.
|
||||
A good way to make resources user friendly is to link to a default script for your resource [example](scripts/add?template=f2d1dc8df796d9e8)`;
|
||||
let selectedResourceType: string | undefined;
|
||||
let resourceType: ResourceType;
|
||||
let resourceSchema: Schema | undefined;
|
||||
let args: Record<string, any> = {};
|
||||
A good way to make resources user friendly is to link to a default script for your resource [example](scripts/add?template=f2d1dc8df796d9e8)`
|
||||
let selectedResourceType: string | undefined
|
||||
let resourceType: ResourceType
|
||||
let resourceSchema: Schema | undefined
|
||||
let args: Record<string, any> = {}
|
||||
|
||||
let error: string | undefined;
|
||||
let error: string | undefined
|
||||
|
||||
let pickForField: string | undefined;
|
||||
let itemPicker: ItemPicker;
|
||||
let variableEditor: VariableEditor;
|
||||
let modal: Modal;
|
||||
let pickForField: string | undefined
|
||||
let itemPicker: ItemPicker
|
||||
let variableEditor: VariableEditor
|
||||
let modal: Modal
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export async function initNew() {
|
||||
selectedResourceType = undefined;
|
||||
step = 1;
|
||||
args = {};
|
||||
path = '';
|
||||
description = '';
|
||||
initialPath = '';
|
||||
resourceSchema = emptySchema();
|
||||
resourceToEdit = undefined;
|
||||
modal.openModal();
|
||||
selectedResourceType = undefined
|
||||
step = 1
|
||||
args = {}
|
||||
path = ''
|
||||
description = ''
|
||||
initialPath = ''
|
||||
resourceSchema = emptySchema()
|
||||
resourceToEdit = undefined
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
export async function initEdit(p: string): Promise<void> {
|
||||
initialPath = p;
|
||||
path = p;
|
||||
step = 2;
|
||||
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p });
|
||||
description = resourceToEdit!.description ?? '';
|
||||
selectedResourceType = resourceToEdit!.resource_type;
|
||||
await loadResourceType();
|
||||
args = resourceToEdit!.value;
|
||||
modal.openModal();
|
||||
initialPath = p
|
||||
path = p
|
||||
step = 2
|
||||
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p })
|
||||
description = resourceToEdit!.description ?? ''
|
||||
selectedResourceType = resourceToEdit!.resource_type
|
||||
await loadResourceType()
|
||||
args = resourceToEdit!.value
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
async function createResource(): Promise<void> {
|
||||
@@ -72,13 +72,13 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
await ResourceService.createResource({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: { path, value: args, description, resource_type: resourceType.name }
|
||||
});
|
||||
sendUserToast(`Successfully created resource at ${path}`);
|
||||
})
|
||||
sendUserToast(`Successfully created resource at ${path}`)
|
||||
|
||||
dispatch('refresh');
|
||||
modal.closeModal();
|
||||
dispatch('refresh')
|
||||
modal.closeModal()
|
||||
} catch (err) {
|
||||
sendUserToast(`${err}`, true);
|
||||
sendUserToast(`${err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,15 +89,15 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
workspace: $workspaceStore!,
|
||||
path: resourceToEdit.path,
|
||||
requestBody: { path, value: args, description }
|
||||
});
|
||||
sendUserToast(`Successfully updated resource at ${path}`);
|
||||
dispatch('refresh');
|
||||
modal.closeModal();
|
||||
})
|
||||
sendUserToast(`Successfully updated resource at ${path}`)
|
||||
dispatch('refresh')
|
||||
modal.closeModal()
|
||||
} else {
|
||||
throw Error('Cannot edit undefined resourceToEdit');
|
||||
throw Error('Cannot edit undefined resourceToEdit')
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(`${err}`, true);
|
||||
sendUserToast(`${err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,25 +106,25 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
resourceType = await ResourceService.getResourceType({
|
||||
workspace: $workspaceStore!,
|
||||
path: selectedResourceType
|
||||
});
|
||||
})
|
||||
|
||||
if (resourceType.schema) {
|
||||
resourceSchema = resourceType.schema as Schema;
|
||||
resourceSchema = resourceType.schema as Schema
|
||||
}
|
||||
} else {
|
||||
sendUserToast(`ResourceType cannot be undefined.`, true);
|
||||
sendUserToast(`ResourceType cannot be undefined.`, true)
|
||||
}
|
||||
}
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {};
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
|
||||
$: isValid = allTrue(inputCheck) ?? false;
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
bind:this={modal}
|
||||
on:close={() => {
|
||||
dispatch('close');
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<div slot="title">{resourceToEdit ? 'Edit ' + resourceToEdit.path : 'Add a resource'}</div>
|
||||
@@ -161,7 +161,7 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
bind:value={selectedResourceType}
|
||||
notPickable={resourceToEdit != undefined}
|
||||
on:click={() => {
|
||||
args = {};
|
||||
args = {}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -189,8 +189,8 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
<button
|
||||
class="default-button-secondary min-w-min items-center leading-4 py-0"
|
||||
on:click={() => {
|
||||
pickForField = fieldName;
|
||||
itemPicker.openModal();
|
||||
pickForField = fieldName
|
||||
itemPicker.openModal()
|
||||
}}>insert variable</button
|
||||
>
|
||||
</div>
|
||||
@@ -208,8 +208,8 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
<button
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
on:click={async () => {
|
||||
await loadResourceType();
|
||||
step = 2;
|
||||
await loadResourceType()
|
||||
step = 2
|
||||
}}
|
||||
>
|
||||
Next
|
||||
@@ -221,7 +221,7 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
<button
|
||||
class="default-button-secondary px-4 py-2 font-semibold"
|
||||
on:click={() => {
|
||||
step = 1;
|
||||
step = 1
|
||||
}}
|
||||
>
|
||||
Back
|
||||
@@ -231,9 +231,9 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
on:click={() => {
|
||||
if (resourceToEdit) {
|
||||
editResource();
|
||||
editResource()
|
||||
} else {
|
||||
createResource();
|
||||
createResource()
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -247,7 +247,7 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
if (pickForField) {
|
||||
args[pickForField] = '$var:' + path;
|
||||
args[pickForField] = '$var:' + path
|
||||
}
|
||||
}}
|
||||
itemName="Variable"
|
||||
@@ -266,7 +266,7 @@ A good way to make resources user friendly is to link to a default script for yo
|
||||
class="default-button-secondary"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
variableEditor.initNew();
|
||||
variableEditor.initNew()
|
||||
}}
|
||||
>
|
||||
Create a new variable
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { type Resource, ResourceService } from '../../gen';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { type Resource, ResourceService } from '../../gen'
|
||||
import { workspaceStore } from '../../stores'
|
||||
|
||||
let resources: Resource[] = [];
|
||||
let resources: Resource[] = []
|
||||
|
||||
export let value: string | undefined;
|
||||
export let value: string | undefined
|
||||
|
||||
export let resourceType: string | undefined;
|
||||
export let resourceType: string | undefined
|
||||
|
||||
async function loadResources(resourceType: string | undefined) {
|
||||
resources = await ResourceService.listResource({ workspace: $workspaceStore!, resourceType });
|
||||
resources = await ResourceService.listResource({ workspace: $workspaceStore!, resourceType })
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadResources(resourceType);
|
||||
loadResources(resourceType)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { ResourceService } from '../../gen';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import IconedResourceType from './IconedResourceType.svelte';
|
||||
import { ResourceService } from '../../gen'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
|
||||
let resources: string[] = [];
|
||||
let resources: string[] = []
|
||||
|
||||
export let value: string | undefined;
|
||||
export let value: string | undefined
|
||||
|
||||
export let notPickable = false;
|
||||
export let notPickable = false
|
||||
|
||||
async function loadResources() {
|
||||
resources = await ResourceService.listResourceTypeNames({ workspace: $workspaceStore! });
|
||||
resources = await ResourceService.listResourceTypeNames({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadResources();
|
||||
loadResources()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -33,8 +33,8 @@
|
||||
? 'item-button-disabled'
|
||||
: 'item-button'}"
|
||||
on:click={() => {
|
||||
value = r;
|
||||
dispatch('click');
|
||||
value = r
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
<IconedResourceType name={r} after={true} />
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import type { Script, Flow } from '../../gen';
|
||||
import { getToday } from '../../utils';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { page } from '$app/stores'
|
||||
import type { Script, Flow } from '../../gen'
|
||||
import { getToday } from '../../utils'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import Icon from 'svelte-awesome';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import SvelteMarkdown from 'svelte-markdown';
|
||||
import SchemaForm from './SchemaForm.svelte';
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
|
||||
export let runnable: Script | Flow | undefined;
|
||||
export let runAction: (scheduledForStr: string | undefined, args: Record<string, any>) => void;
|
||||
export let buttonText = 'Run';
|
||||
export let schedulable = true;
|
||||
export let detailed = true;
|
||||
export let runnable: Script | Flow | undefined
|
||||
export let runAction: (scheduledForStr: string | undefined, args: Record<string, any>) => void
|
||||
export let buttonText = 'Run'
|
||||
export let schedulable = true
|
||||
export let detailed = true
|
||||
|
||||
export let args: Record<string, any> = {};
|
||||
export let args: Record<string, any> = {}
|
||||
|
||||
let isValid = true;
|
||||
let isValid = true
|
||||
|
||||
let queryArgs = $page.url.searchParams.get('args');
|
||||
let queryArgs = $page.url.searchParams.get('args')
|
||||
if (queryArgs) {
|
||||
const parsed = JSON.parse(atob(queryArgs));
|
||||
const parsed = JSON.parse(atob(queryArgs))
|
||||
Object.entries(parsed).forEach(([k, v]) => {
|
||||
if (v == '<function call>') {
|
||||
parsed[k] = undefined;
|
||||
parsed[k] = undefined
|
||||
}
|
||||
});
|
||||
console.log(parsed);
|
||||
args = parsed;
|
||||
})
|
||||
console.log(parsed)
|
||||
args = parsed
|
||||
}
|
||||
|
||||
// Run later
|
||||
let viewOptions = false;
|
||||
let scheduledForStr: string | undefined;
|
||||
let viewOptions = false
|
||||
let scheduledForStr: string | undefined
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl">
|
||||
@@ -97,7 +97,7 @@
|
||||
<button
|
||||
class="default-button-secondary mx-2 mb-1"
|
||||
on:click={() => {
|
||||
scheduledForStr = undefined;
|
||||
scheduledForStr = undefined
|
||||
}}>clear</button
|
||||
>
|
||||
</div>
|
||||
@@ -109,7 +109,7 @@
|
||||
type="submit"
|
||||
class="mr-6 text-sm underline text-gray-700 inline-flex items-center"
|
||||
on:click={() => {
|
||||
viewOptions = !viewOptions;
|
||||
viewOptions = !viewOptions
|
||||
}}
|
||||
>
|
||||
{#if schedulable}
|
||||
@@ -126,7 +126,7 @@
|
||||
disabled={!isValid}
|
||||
class="{isValid ? 'default-button' : 'default-button-disabled'} w-min px-6"
|
||||
on:click={() => {
|
||||
runAction(scheduledForStr, args);
|
||||
runAction(scheduledForStr, args)
|
||||
}}
|
||||
>
|
||||
{scheduledForStr ? 'Schedule run to a later time' : buttonText}
|
||||
|
||||
@@ -1,130 +1,126 @@
|
||||
<script lang="ts">
|
||||
import SchemaModal, {
|
||||
DEFAULT_PROPERTY,
|
||||
modalToSchema,
|
||||
schemaToModal
|
||||
} from './SchemaModal.svelte';
|
||||
import type { ModalSchemaProperty } from './SchemaModal.svelte';
|
||||
import type { Schema } from '../../common';
|
||||
import Editor from './Editor.svelte';
|
||||
import { emptySchema, sendUserToast } from '../../utils';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import TableCustom from './TableCustom.svelte';
|
||||
import SchemaModal, { DEFAULT_PROPERTY, modalToSchema, schemaToModal } from './SchemaModal.svelte'
|
||||
import type { ModalSchemaProperty } from './SchemaModal.svelte'
|
||||
import type { Schema } from '../../common'
|
||||
import Editor from './Editor.svelte'
|
||||
import { emptySchema, sendUserToast } from '../../utils'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
|
||||
export let schema: Schema = emptySchema();
|
||||
export let schema: Schema = emptySchema()
|
||||
|
||||
let schemaModal: SchemaModal;
|
||||
let schemaString: string = '';
|
||||
let schemaModal: SchemaModal
|
||||
let schemaString: string = ''
|
||||
|
||||
// Internal state: bound to args builder modal
|
||||
let modalProperty: ModalSchemaProperty = Object.assign({}, DEFAULT_PROPERTY);
|
||||
let argError = '';
|
||||
let editing = false;
|
||||
let oldArgName: string | undefined; // when editing argument and changing name
|
||||
let modalProperty: ModalSchemaProperty = Object.assign({}, DEFAULT_PROPERTY)
|
||||
let argError = ''
|
||||
let editing = false
|
||||
let oldArgName: string | undefined // when editing argument and changing name
|
||||
|
||||
let viewJsonSchema = false;
|
||||
let editor: Editor;
|
||||
let viewJsonSchema = false
|
||||
let editor: Editor
|
||||
|
||||
$: schemaString = JSON.stringify(schema, null, '\t');
|
||||
$: schemaString = JSON.stringify(schema, null, '\t')
|
||||
|
||||
export function getEditor(): Editor {
|
||||
return editor;
|
||||
return editor
|
||||
}
|
||||
// Binding is not enough because monaco Editor does not support two-way binding
|
||||
export function getSchema(): Schema {
|
||||
if (viewJsonSchema) {
|
||||
try {
|
||||
schema = JSON.parse(editor.getCode());
|
||||
return schema;
|
||||
schema = JSON.parse(editor.getCode())
|
||||
return schema
|
||||
} catch (err) {
|
||||
throw Error(`Error: input is not a valid schema: ${err}`);
|
||||
throw Error(`Error: input is not a valid schema: ${err}`)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
editor.setCode(JSON.stringify(schema, null, '\t'));
|
||||
return schema;
|
||||
editor.setCode(JSON.stringify(schema, null, '\t'))
|
||||
return schema
|
||||
} catch (err) {
|
||||
throw Error(`Error: input is not a valid schema: ${err}`);
|
||||
throw Error(`Error: input is not a valid schema: ${err}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleAddOrEditArgument(): void {
|
||||
// If editing the arg's name, oldName containing the old argument name must be provided
|
||||
argError = '';
|
||||
argError = ''
|
||||
if (modalProperty.name.length === 0) {
|
||||
argError = 'Arguments need to have a name';
|
||||
argError = 'Arguments need to have a name'
|
||||
} else if (Object.keys(schema.properties).includes(modalProperty.name) && !editing) {
|
||||
argError = 'There is already an argument with this name';
|
||||
argError = 'There is already an argument with this name'
|
||||
} else {
|
||||
schema.properties[modalProperty.name] = modalToSchema(modalProperty);
|
||||
schema.properties[modalProperty.name] = modalToSchema(modalProperty)
|
||||
if (modalProperty.required) {
|
||||
schema.required = [...schema.required, modalProperty.name];
|
||||
schema.required = [...schema.required, modalProperty.name]
|
||||
} else if (schema.required.includes(modalProperty.name)) {
|
||||
const index = schema.required.indexOf(modalProperty.name, 0);
|
||||
const index = schema.required.indexOf(modalProperty.name, 0)
|
||||
if (index > -1) {
|
||||
schema.required.splice(index, 1);
|
||||
schema.required.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (editing && oldArgName && oldArgName !== modalProperty.name) {
|
||||
handleDeleteArgument(oldArgName);
|
||||
handleDeleteArgument(oldArgName)
|
||||
}
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY);
|
||||
editing = false;
|
||||
oldArgName = undefined;
|
||||
schemaModal.closeModal();
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
|
||||
editing = false
|
||||
oldArgName = undefined
|
||||
schemaModal.closeModal()
|
||||
}
|
||||
}
|
||||
|
||||
function startEditArgument(argName: string): void {
|
||||
argError = '';
|
||||
argError = ''
|
||||
if (Object.keys(schema.properties).includes(argName)) {
|
||||
schemaModal.openModal();
|
||||
editing = true;
|
||||
schemaModal.openModal()
|
||||
editing = true
|
||||
modalProperty = schemaToModal(
|
||||
schema.properties[argName],
|
||||
argName,
|
||||
schema.required.includes(argName)
|
||||
);
|
||||
oldArgName = argName;
|
||||
)
|
||||
oldArgName = argName
|
||||
} else {
|
||||
sendUserToast(`This argument does not exist and can't be edited`, true);
|
||||
sendUserToast(`This argument does not exist and can't be edited`, true)
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteArgument(argName: string): void {
|
||||
try {
|
||||
if (Object.keys(schema.properties).includes(argName)) {
|
||||
delete schema.properties[argName];
|
||||
schema = schema; //needed for reactivity, see https://svelte.dev/tutorial/updating-arrays-and-objects
|
||||
delete schema.properties[argName]
|
||||
schema = schema //needed for reactivity, see https://svelte.dev/tutorial/updating-arrays-and-objects
|
||||
} else {
|
||||
throw Error('Argument not found!');
|
||||
throw Error('Argument not found!')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
sendUserToast(`Could not delete argument: ${err}`, true);
|
||||
console.error(err)
|
||||
sendUserToast(`Could not delete argument: ${err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(): void {
|
||||
if (viewJsonSchema) {
|
||||
let schemaString = editor.getCode();
|
||||
let schemaString = editor.getCode()
|
||||
if (schemaString === '') {
|
||||
schemaString = JSON.stringify(emptySchema(), null, 4);
|
||||
schemaString = JSON.stringify(emptySchema(), null, 4)
|
||||
}
|
||||
try {
|
||||
schema = JSON.parse(schemaString);
|
||||
viewJsonSchema = false;
|
||||
schema = JSON.parse(schemaString)
|
||||
viewJsonSchema = false
|
||||
} catch (err) {
|
||||
sendUserToast(err, true);
|
||||
sendUserToast(err, true)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
editor.setCode(JSON.stringify(schema, null, '\t'));
|
||||
viewJsonSchema = true;
|
||||
editor.setCode(JSON.stringify(schema, null, '\t'))
|
||||
viewJsonSchema = true
|
||||
} catch (err) {
|
||||
sendUserToast(err, true);
|
||||
sendUserToast(err, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,8 +151,8 @@
|
||||
<button
|
||||
class="default-button-secondary grow"
|
||||
on:click={() => {
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY);
|
||||
schemaModal.openModal();
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
|
||||
schemaModal.openModal()
|
||||
}}>Add argument</button
|
||||
>
|
||||
</div>
|
||||
@@ -203,7 +199,7 @@
|
||||
<button
|
||||
class="default-button-secondary text-xs inline-flex"
|
||||
on:click={() => {
|
||||
startEditArgument(name);
|
||||
startEditArgument(name)
|
||||
}}>edit</button
|
||||
></td
|
||||
>
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { allTrue } from '../../utils';
|
||||
import { allTrue } from '../../utils'
|
||||
|
||||
import { slide } from 'svelte/transition';
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
import type { Schema } from '../../common';
|
||||
import ArgInput from './ArgInput.svelte';
|
||||
import RadioButton from './RadioButton.svelte';
|
||||
import Editor from './Editor.svelte';
|
||||
import FieldHeader from './FieldHeader.svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import type { Schema } from '../../common'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import Editor from './Editor.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
export let inputTransform = false;
|
||||
export let schema: Schema;
|
||||
export let args: Record<string, any> = {};
|
||||
export let inputTransform = false
|
||||
export let schema: Schema
|
||||
export let args: Record<string, any> = {}
|
||||
|
||||
export let isValid: boolean = true;
|
||||
export let editableSchema = false;
|
||||
export let isValid: boolean = true
|
||||
export let editableSchema = false
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {};
|
||||
let seeHelp: { [id: string]: boolean } = {};
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
let seeHelp: { [id: string]: boolean } = {}
|
||||
|
||||
export function setArgs(nargs: Record<string, any>) {
|
||||
args = nargs;
|
||||
args = nargs
|
||||
}
|
||||
|
||||
$: isValid = allTrue(inputCheck) ?? false;
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
@@ -51,13 +51,13 @@
|
||||
small={true}
|
||||
bind:value={args[argName].type}
|
||||
on:change={(e) => {
|
||||
console.log(e.detail);
|
||||
console.log(e.detail)
|
||||
args[argName].expr =
|
||||
e.detail == 'javascript'
|
||||
? `import { previous_result, flow_input, step, variable, resource, params } from 'windmill'
|
||||
|
||||
previous_result.myfield`
|
||||
: undefined;
|
||||
: undefined
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@ previous_result.myfield`
|
||||
<span
|
||||
class="underline mr-4"
|
||||
on:click={() => {
|
||||
seeHelp[argName] = seeHelp[argName] == undefined ? true : !seeHelp[argName];
|
||||
seeHelp[argName] = seeHelp[argName] == undefined ? true : !seeHelp[argName]
|
||||
}}
|
||||
>Help<Icon
|
||||
class="ml-2"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { SchemaProperty } from '../../common';
|
||||
import Modal from '../../routes/components/Modal.svelte';
|
||||
import type { SchemaProperty } from '../../common'
|
||||
import Modal from '../../routes/components/Modal.svelte'
|
||||
|
||||
export const ARG_TYPES = ['integer', 'number', 'string', 'boolean', 'object', 'array'] as const;
|
||||
export type ArgType = typeof ARG_TYPES[number];
|
||||
export const ARG_TYPES = ['integer', 'number', 'string', 'boolean', 'object', 'array'] as const
|
||||
export type ArgType = typeof ARG_TYPES[number]
|
||||
|
||||
export interface ModalSchemaProperty {
|
||||
selectedType?: string;
|
||||
description: string;
|
||||
name: string;
|
||||
required: boolean;
|
||||
format?: string;
|
||||
pattern?: string;
|
||||
enum_?: string[];
|
||||
default?: any;
|
||||
items?: { type?: 'string' | 'number' };
|
||||
selectedType?: string
|
||||
description: string
|
||||
name: string
|
||||
required: boolean
|
||||
format?: string
|
||||
pattern?: string
|
||||
enum_?: string[]
|
||||
default?: any
|
||||
items?: { type?: 'string' | 'number' }
|
||||
}
|
||||
|
||||
export function modalToSchema(schema: ModalSchemaProperty): SchemaProperty {
|
||||
@@ -25,7 +25,7 @@
|
||||
default: schema.default,
|
||||
enum: schema.enum_,
|
||||
items: schema.items
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function schemaToModal(
|
||||
@@ -40,7 +40,7 @@
|
||||
pattern: schema.pattern,
|
||||
default: schema.default,
|
||||
required
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFAULT_PROPERTY: ModalSchemaProperty = {
|
||||
@@ -48,41 +48,41 @@
|
||||
description: '',
|
||||
name: '',
|
||||
required: false
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Switch from './Switch.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import ArgInput from './ArgInput.svelte';
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte';
|
||||
import Required from './Required.svelte';
|
||||
import Switch from './Switch.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
import Required from './Required.svelte'
|
||||
|
||||
export let property: ModalSchemaProperty = DEFAULT_PROPERTY;
|
||||
export let error = '';
|
||||
export let editing = false;
|
||||
export let oldArgName: string | undefined;
|
||||
export let property: ModalSchemaProperty = DEFAULT_PROPERTY
|
||||
export let error = ''
|
||||
export let editing = false
|
||||
export let oldArgName: string | undefined
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let modal: Modal;
|
||||
const dispatch = createEventDispatcher()
|
||||
let modal: Modal
|
||||
|
||||
export function openModal(): void {
|
||||
modal.openModal();
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
export function closeModal(): void {
|
||||
modal.closeModal();
|
||||
modal.closeModal()
|
||||
}
|
||||
|
||||
function clearModal(): void {
|
||||
error = '';
|
||||
editing = false;
|
||||
oldArgName = undefined;
|
||||
property.name = DEFAULT_PROPERTY.name;
|
||||
property.default = DEFAULT_PROPERTY.default;
|
||||
property.description = DEFAULT_PROPERTY.description;
|
||||
property.required = DEFAULT_PROPERTY.required;
|
||||
property.selectedType = DEFAULT_PROPERTY.selectedType;
|
||||
error = ''
|
||||
editing = false
|
||||
oldArgName = undefined
|
||||
property.name = DEFAULT_PROPERTY.name
|
||||
property.default = DEFAULT_PROPERTY.default
|
||||
property.description = DEFAULT_PROPERTY.description
|
||||
property.required = DEFAULT_PROPERTY.required
|
||||
property.selectedType = DEFAULT_PROPERTY.selectedType
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -111,14 +111,14 @@
|
||||
<button
|
||||
class={argType == property.selectedType ? 'item-button-selected' : 'item-button'}
|
||||
on:click={() => {
|
||||
property.selectedType = argType;
|
||||
property.selectedType = argType
|
||||
}}>{argType}</button
|
||||
>
|
||||
{/each}
|
||||
<button
|
||||
class={!property.selectedType ? 'item-button-selected' : 'item-button'}
|
||||
on:click={() => {
|
||||
property.selectedType = undefined;
|
||||
property.selectedType = undefined
|
||||
}}>any</button
|
||||
>
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@
|
||||
slot="submission"
|
||||
class="px-4 py-2 text-white font-semibold bg-blue-500 rounded"
|
||||
on:click={() => {
|
||||
dispatch('save');
|
||||
dispatch('save')
|
||||
}}
|
||||
>
|
||||
Save
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '../../common';
|
||||
import { emptySchema } from '../../utils';
|
||||
import type { Schema } from '../../common'
|
||||
import { emptySchema } from '../../utils'
|
||||
|
||||
import Highlight from 'svelte-highlight';
|
||||
import json from 'svelte-highlight/src/languages/json';
|
||||
import github from 'svelte-highlight/src/styles/github';
|
||||
import TableCustom from './TableCustom.svelte';
|
||||
import Highlight from 'svelte-highlight'
|
||||
import json from 'svelte-highlight/src/languages/json'
|
||||
import github from 'svelte-highlight/src/styles/github'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
|
||||
export let schema: Schema | undefined = emptySchema();
|
||||
export let schema: Schema | undefined = emptySchema()
|
||||
|
||||
let viewJsonSchema = false;
|
||||
let viewJsonSchema = false
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { ScriptService, type Script } from '../../gen';
|
||||
import { ScriptService, type Script } from '../../gen'
|
||||
|
||||
import { emptySchema, sendUserToast } from '../../utils';
|
||||
import { onDestroy } from 'svelte';
|
||||
import ScriptEditor from './ScriptEditor.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import Path from './Path.svelte';
|
||||
import SvelteMarkdown from 'svelte-markdown';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import ScriptSchema from './ScriptSchema.svelte';
|
||||
import { inferArgs } from '../../infer';
|
||||
import Required from './Required.svelte';
|
||||
import { emptySchema, sendUserToast } from '../../utils'
|
||||
import { onDestroy } from 'svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import Path from './Path.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import { inferArgs } from '../../infer'
|
||||
import Required from './Required.svelte'
|
||||
|
||||
let editor: ScriptEditor;
|
||||
let scriptSchema: ScriptSchema;
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1;
|
||||
let editor: ScriptEditor
|
||||
let scriptSchema: ScriptSchema
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1
|
||||
|
||||
export let script: Script;
|
||||
export let initialPath: string = '';
|
||||
export let script: Script
|
||||
export let initialPath: string = ''
|
||||
|
||||
$: {
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(script)));
|
||||
history.replaceState({}, '', $page.url);
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(script)))
|
||||
history.replaceState({}, '', $page.url)
|
||||
}
|
||||
|
||||
async function editScript(): Promise<void> {
|
||||
@@ -38,47 +38,42 @@
|
||||
schema: script.schema,
|
||||
is_template: script.is_template
|
||||
}
|
||||
});
|
||||
sendUserToast(`Success! New script version created with hash ${newHash}`);
|
||||
goto(`/scripts/get/${newHash}`);
|
||||
})
|
||||
sendUserToast(`Success! New script version created with hash ${newHash}`)
|
||||
goto(`/scripts/get/${newHash}`)
|
||||
} catch (error) {
|
||||
if (error.status === 400) {
|
||||
sendUserToast(error.body, true);
|
||||
} else {
|
||||
sendUserToast(`Ooops.Something bad happened: ${error}`, true);
|
||||
console.error(error);
|
||||
}
|
||||
sendUserToast(`Impossible to save the script: ${error.body}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
export function setCode(script: Script) {
|
||||
editor?.getEditor().setCode(script.content);
|
||||
editor?.getEditor().setCode(script.content)
|
||||
|
||||
if (scriptSchema) {
|
||||
if (script.schema) {
|
||||
scriptSchema.setSchema(script.schema);
|
||||
scriptSchema.setSchema(script.schema)
|
||||
} else {
|
||||
scriptSchema.setSchema(emptySchema());
|
||||
scriptSchema.setSchema(emptySchema())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function inferSchema() {
|
||||
await inferArgs(script.content, script.schema);
|
||||
await inferArgs(script.content, script.schema)
|
||||
}
|
||||
|
||||
async function changeStep(step: number) {
|
||||
if (step == 3) {
|
||||
script.content = editor?.getEditor().getCode() ?? script.content;
|
||||
await inferSchema();
|
||||
script.schema = script.schema;
|
||||
script.content = editor?.getEditor().getCode() ?? script.content
|
||||
await inferSchema()
|
||||
script.schema = script.schema
|
||||
}
|
||||
goto(`?step=${step}`);
|
||||
goto(`?step=${step}`)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
editor?.$destroy();
|
||||
});
|
||||
editor?.$destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-screen max-w-screen-lg xl:-ml-20 xl:pl-4 w-full -mt-4 pt-4 md:mx-10 ">
|
||||
@@ -91,7 +86,7 @@
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(1);
|
||||
changeStep(1)
|
||||
}}>Step 1: Metadata</button
|
||||
>
|
||||
<button
|
||||
@@ -99,7 +94,7 @@
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(2);
|
||||
changeStep(2)
|
||||
}}>Step 2: Code</button
|
||||
>
|
||||
<button
|
||||
@@ -107,7 +102,7 @@
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(3);
|
||||
changeStep(3)
|
||||
}}>Step 3: UI customisation</button
|
||||
>
|
||||
</div>
|
||||
@@ -118,15 +113,15 @@
|
||||
(script.path == undefined || script.path == '' || script.path.split('/')[2] == '')}
|
||||
class="default-button px-6 max-h-8"
|
||||
on:click={() => {
|
||||
changeStep(step + 1);
|
||||
changeStep(step + 1)
|
||||
}}>Next</button
|
||||
>
|
||||
{#if step == 2}
|
||||
<button
|
||||
class="default-button-secondary px-6 max-h-8 mr-2"
|
||||
on:click={async () => {
|
||||
await inferSchema();
|
||||
editScript();
|
||||
await inferSchema()
|
||||
editScript()
|
||||
}}>Save (commit)</button
|
||||
>
|
||||
{/if}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
VariableService,
|
||||
ResourceService,
|
||||
ScriptService
|
||||
} from '../../gen';
|
||||
import { sendUserToast, emptySchema, displayDate } from '../../utils';
|
||||
import type { Schema } from '../../common';
|
||||
import { fade } from 'svelte/transition';
|
||||
import Icon from 'svelte-awesome';
|
||||
} from '../../gen'
|
||||
import { sendUserToast, emptySchema, displayDate } from '../../utils'
|
||||
import type { Schema } from '../../common'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Icon from 'svelte-awesome'
|
||||
import {
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
@@ -20,87 +20,87 @@
|
||||
faSearch,
|
||||
faSpinner,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import Editor from './Editor.svelte';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { userStore, workspaceStore } from '../../stores';
|
||||
import TableCustom from './TableCustom.svelte';
|
||||
import { check } from 'svelte-awesome/icons';
|
||||
import Modal from './Modal.svelte';
|
||||
import { Highlight } from 'svelte-highlight';
|
||||
import { json, python } from 'svelte-highlight/src/languages';
|
||||
import github from 'svelte-highlight/src/styles/github';
|
||||
import ItemPicker from './ItemPicker.svelte';
|
||||
import VariableEditor from './VariableEditor.svelte';
|
||||
import ResourceEditor from './ResourceEditor.svelte';
|
||||
import { inferArgs } from '../../infer';
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Editor from './Editor.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { userStore, workspaceStore } from '../../stores'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { check } from 'svelte-awesome/icons'
|
||||
import Modal from './Modal.svelte'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { json, python } from 'svelte-highlight/src/languages'
|
||||
import github from 'svelte-highlight/src/styles/github'
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
import ResourceEditor from './ResourceEditor.svelte'
|
||||
import { inferArgs } from '../../infer'
|
||||
|
||||
// @ts-ignore
|
||||
import { VSplitPane } from 'svelte-split-pane';
|
||||
import SchemaForm from './SchemaForm.svelte';
|
||||
import DisplayResult from './DisplayResult.svelte';
|
||||
import { VSplitPane } from 'svelte-split-pane'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
|
||||
// Exported
|
||||
export let schema: Schema = emptySchema();
|
||||
export let schema: Schema = emptySchema()
|
||||
|
||||
export let code: string;
|
||||
export let path: string | undefined;
|
||||
export let code: string
|
||||
export let path: string | undefined
|
||||
|
||||
// Control Editor layout
|
||||
export let viewPreview = true;
|
||||
export let previewTab: 'logs' | 'input' | 'output' | 'history' | 'last_save' = 'logs';
|
||||
export let viewPreview = true
|
||||
export let previewTab: 'logs' | 'input' | 'output' | 'history' | 'last_save' = 'logs'
|
||||
|
||||
let websocketAlive = { pyright: false, black: false };
|
||||
let websocketAlive = { pyright: false, black: false }
|
||||
|
||||
// Internal state
|
||||
let editor: Editor;
|
||||
let editor: Editor
|
||||
|
||||
// Preview args input
|
||||
let args: Record<string, any> = {};
|
||||
let isValid: boolean = true;
|
||||
let args: Record<string, any> = {}
|
||||
let isValid: boolean = true
|
||||
|
||||
// Preview
|
||||
let previewIsLoading = false;
|
||||
let previewIntervalId: NodeJS.Timer;
|
||||
let previewJob: Job | undefined;
|
||||
let pastPreviews: CompletedJob[] = [];
|
||||
let previewIsLoading = false
|
||||
let previewIntervalId: NodeJS.Timer
|
||||
let previewJob: Job | undefined
|
||||
let pastPreviews: CompletedJob[] = []
|
||||
|
||||
let modalViewer: Modal;
|
||||
let modalViewerTitle: string = '';
|
||||
let modalViewerContent: any;
|
||||
let modalViewerMode: 'logs' | 'result' | 'code' = 'logs';
|
||||
let modalViewer: Modal
|
||||
let modalViewerTitle: string = ''
|
||||
let modalViewerContent: any
|
||||
let modalViewerMode: 'logs' | 'result' | 'code' = 'logs'
|
||||
|
||||
let variablePicker: ItemPicker;
|
||||
let resourcePicker: ItemPicker;
|
||||
let scriptPicker: ItemPicker;
|
||||
let variableEditor: VariableEditor;
|
||||
let resourceEditor: ResourceEditor;
|
||||
let variablePicker: ItemPicker
|
||||
let resourcePicker: ItemPicker
|
||||
let scriptPicker: ItemPicker
|
||||
let variableEditor: VariableEditor
|
||||
let resourceEditor: ResourceEditor
|
||||
|
||||
let syncIteration: number = 0;
|
||||
let ITERATIONS_BEFORE_SLOW_REFRESH = 100;
|
||||
let syncIteration: number = 0
|
||||
let ITERATIONS_BEFORE_SLOW_REFRESH = 100
|
||||
|
||||
let lastSave: string | null;
|
||||
let lastSave: string | null
|
||||
|
||||
$: lastSave = localStorage.getItem(path ?? 'last_save');
|
||||
$: lastSave = localStorage.getItem(path ?? 'last_save')
|
||||
|
||||
export function getEditor(): Editor {
|
||||
return editor;
|
||||
return editor
|
||||
}
|
||||
|
||||
export async function runPreview(): Promise<void> {
|
||||
try {
|
||||
if (previewIntervalId) {
|
||||
clearInterval(previewIntervalId);
|
||||
clearInterval(previewIntervalId)
|
||||
}
|
||||
if (previewIsLoading && previewJob) {
|
||||
JobService.cancelQueuedJob({
|
||||
workspace: $workspaceStore!,
|
||||
id: previewJob.id,
|
||||
requestBody: {}
|
||||
});
|
||||
})
|
||||
}
|
||||
previewIsLoading = true;
|
||||
previewIsLoading = true
|
||||
|
||||
const previewId = await JobService.runScriptPreview({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -109,17 +109,17 @@
|
||||
content: editor.getCode(),
|
||||
args: args
|
||||
}
|
||||
});
|
||||
previewJob = undefined;
|
||||
loadPreviewJob(previewId);
|
||||
syncIteration = 0;
|
||||
})
|
||||
previewJob = undefined
|
||||
loadPreviewJob(previewId)
|
||||
syncIteration = 0
|
||||
previewIntervalId = setInterval(() => {
|
||||
syncer(previewId);
|
||||
}, 500);
|
||||
syncer(previewId)
|
||||
}, 500)
|
||||
//TODO fetch preview, every x time, until it's completed
|
||||
} catch (err) {
|
||||
previewIsLoading = false;
|
||||
sendUserToast(`Could not run preview: ${err} `, true);
|
||||
previewIsLoading = false
|
||||
sendUserToast(`Could not run preview: ${err} `, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
jobKinds: 'preview',
|
||||
createdBy: $userStore?.username,
|
||||
scriptPathExact: path
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function loadPreviewJob(id: string): Promise<void> {
|
||||
@@ -140,98 +140,98 @@
|
||||
id,
|
||||
running: previewJob.running,
|
||||
logOffset: previewJob.logs?.length ?? 0
|
||||
});
|
||||
})
|
||||
|
||||
if (previewJobUpdates.new_logs) {
|
||||
previewJob.logs = (previewJob.logs ?? '').concat(previewJobUpdates.new_logs);
|
||||
previewJob.logs = (previewJob.logs ?? '').concat(previewJobUpdates.new_logs)
|
||||
}
|
||||
if ((previewJobUpdates.running ?? false) || (previewJobUpdates.completed ?? false)) {
|
||||
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id });
|
||||
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id })
|
||||
}
|
||||
} else {
|
||||
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id });
|
||||
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id })
|
||||
}
|
||||
if (previewJob?.type === 'CompletedJob') {
|
||||
//only CompletedJob has success property
|
||||
clearInterval(previewIntervalId);
|
||||
previewIsLoading = false;
|
||||
loadPastPreviews();
|
||||
clearInterval(previewIntervalId)
|
||||
previewIsLoading = false
|
||||
loadPastPreviews()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function inferSchema() {
|
||||
let isDefault: string[] = [];
|
||||
let isDefault: string[] = []
|
||||
Object.entries(args).forEach(([k, v]) => {
|
||||
if (schema.properties[k].default == v) {
|
||||
isDefault.push(k);
|
||||
isDefault.push(k)
|
||||
}
|
||||
});
|
||||
await inferArgs(editor.getCode(), schema);
|
||||
schema = schema;
|
||||
})
|
||||
await inferArgs(editor.getCode(), schema)
|
||||
schema = schema
|
||||
|
||||
isDefault.forEach((key) => (args[key] = schema.properties[key].default));
|
||||
isDefault.forEach((key) => (args[key] = schema.properties[key].default))
|
||||
for (const key of Object.keys(args)) {
|
||||
if (schema.properties[key] == undefined) {
|
||||
delete args[key];
|
||||
delete args[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncer(id: string): void {
|
||||
if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) {
|
||||
loadPreviewJob(id);
|
||||
loadPreviewJob(id)
|
||||
if (previewIntervalId) {
|
||||
clearInterval(previewIntervalId);
|
||||
previewIntervalId = setInterval(() => loadPreviewJob(id), 5000);
|
||||
clearInterval(previewIntervalId)
|
||||
previewIntervalId = setInterval(() => loadPreviewJob(id), 5000)
|
||||
}
|
||||
} else {
|
||||
syncIteration++;
|
||||
loadPreviewJob(id);
|
||||
syncIteration++
|
||||
loadPreviewJob(id)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadVariables() {
|
||||
let r: { name: string; path?: string; description?: string }[] = [];
|
||||
let r: { name: string; path?: string; description?: string }[] = []
|
||||
const variables = (
|
||||
await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })
|
||||
).map((x) => {
|
||||
return { name: x.path, ...x };
|
||||
});
|
||||
return { name: x.path, ...x }
|
||||
})
|
||||
|
||||
const rvariables = await VariableService.listContextualVariables({
|
||||
workspace: $workspaceStore ?? 'NO_W'
|
||||
});
|
||||
r = r.concat(variables).concat(rvariables);
|
||||
return r;
|
||||
})
|
||||
r = r.concat(variables).concat(rvariables)
|
||||
return r
|
||||
}
|
||||
|
||||
async function loadScripts(): Promise<{ path: string; summary?: string }[]> {
|
||||
return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' });
|
||||
return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' })
|
||||
}
|
||||
|
||||
let syncCode: NodeJS.Timer;
|
||||
let syncCode: NodeJS.Timer
|
||||
onMount(() => {
|
||||
syncCode = setInterval(() => {
|
||||
const newCode = editor?.getCode();
|
||||
const newCode = editor?.getCode()
|
||||
if (newCode && code != newCode) {
|
||||
code = editor.getCode();
|
||||
code = editor.getCode()
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
}, 3000)
|
||||
})
|
||||
onDestroy(() => {
|
||||
if (editor) {
|
||||
code = editor.getCode();
|
||||
code = editor.getCode()
|
||||
}
|
||||
if (previewIntervalId) {
|
||||
clearInterval(previewIntervalId);
|
||||
clearInterval(previewIntervalId)
|
||||
}
|
||||
if (syncCode) {
|
||||
clearInterval(syncCode);
|
||||
clearInterval(syncCode)
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -241,15 +241,15 @@
|
||||
<ItemPicker
|
||||
bind:this={scriptPicker}
|
||||
pickCallback={async (path, _) => {
|
||||
modalViewerMode = 'code';
|
||||
modalViewerTitle = 'Script ' + path;
|
||||
modalViewerMode = 'code'
|
||||
modalViewerTitle = 'Script ' + path
|
||||
modalViewerContent = (
|
||||
await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path
|
||||
})
|
||||
).content;
|
||||
modalViewer.openModal();
|
||||
).content
|
||||
modalViewer.openModal()
|
||||
}}
|
||||
closeOnClick={false}
|
||||
itemName="script"
|
||||
@@ -277,16 +277,16 @@
|
||||
pickCallback={(path, name) => {
|
||||
if (!path) {
|
||||
if (!getEditor().getCode().includes('import os')) {
|
||||
getEditor().insertAtBeginning('import os\n');
|
||||
getEditor().insertAtBeginning('import os\n')
|
||||
}
|
||||
getEditor().insertAtCursor(`os.environ.get("${name}")`);
|
||||
sendUserToast(`${name} inserted at cursor`);
|
||||
getEditor().insertAtCursor(`os.environ.get("${name}")`)
|
||||
sendUserToast(`${name} inserted at cursor`)
|
||||
} else {
|
||||
if (!getEditor().getCode().includes('import wmill')) {
|
||||
getEditor().insertAtBeginning('import wmill\n');
|
||||
getEditor().insertAtBeginning('import wmill\n')
|
||||
}
|
||||
getEditor().insertAtCursor(`wmill.get_variable("${path}")`);
|
||||
sendUserToast(`${name} inserted at cursor`);
|
||||
getEditor().insertAtCursor(`wmill.get_variable("${path}")`)
|
||||
sendUserToast(`${name} inserted at cursor`)
|
||||
}
|
||||
}}
|
||||
itemName="Variable"
|
||||
@@ -301,7 +301,7 @@
|
||||
class="default-button-secondary"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
variableEditor.initNew();
|
||||
variableEditor.initNew()
|
||||
}}
|
||||
>
|
||||
Create a new variable
|
||||
@@ -312,8 +312,8 @@
|
||||
<ItemPicker
|
||||
bind:this={resourcePicker}
|
||||
pickCallback={(path, _) => {
|
||||
getEditor().insertAtCursor(`client.get_resource("${path}")`);
|
||||
sendUserToast(`${path} inserted at cursor`);
|
||||
getEditor().insertAtCursor(`client.get_resource("${path}")`)
|
||||
sendUserToast(`${path} inserted at cursor`)
|
||||
}}
|
||||
itemName="Resource"
|
||||
extraField="resource_type"
|
||||
@@ -328,7 +328,7 @@
|
||||
class="default-button-secondary"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
resourceEditor.initNew();
|
||||
resourceEditor.initNew()
|
||||
}}
|
||||
>
|
||||
Create a new resource
|
||||
@@ -346,7 +346,7 @@
|
||||
downPanelSize={viewPreview ? '25%' : '10%'}
|
||||
updateCallback={() => {
|
||||
if (!viewPreview) {
|
||||
viewPreview = true;
|
||||
viewPreview = true
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -357,7 +357,7 @@
|
||||
<button
|
||||
class="default-button-secondary font-semibold py-px mr-2 text-xs align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
variablePicker.openModal();
|
||||
variablePicker.openModal()
|
||||
}}
|
||||
>Variable picker <Icon data={faSearch} scale={0.7} />
|
||||
</button>
|
||||
@@ -365,7 +365,7 @@
|
||||
<button
|
||||
class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
resourcePicker.openModal();
|
||||
resourcePicker.openModal()
|
||||
}}
|
||||
>Resource picker <Icon data={faSearch} scale={0.7} />
|
||||
</button>
|
||||
@@ -373,7 +373,7 @@
|
||||
<button
|
||||
class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
scriptPicker.openModal();
|
||||
scriptPicker.openModal()
|
||||
}}
|
||||
>Script explorer <Icon data={faSearch} scale={0.7} />
|
||||
</button>
|
||||
@@ -381,7 +381,7 @@
|
||||
<button
|
||||
class="default-button-secondary py-px max-h-8 text-xs"
|
||||
on:click|stopPropagation={() => {
|
||||
editor.reloadWebsocket();
|
||||
editor.reloadWebsocket()
|
||||
}}
|
||||
>
|
||||
Reload assistants (status: <span
|
||||
@@ -396,12 +396,12 @@
|
||||
bind:websocketAlive
|
||||
bind:this={editor}
|
||||
cmdEnterAction={() => {
|
||||
runPreview();
|
||||
viewPreview = true;
|
||||
runPreview()
|
||||
viewPreview = true
|
||||
}}
|
||||
formatAction={() => {
|
||||
code = getEditor().getCode();
|
||||
localStorage.setItem(path ?? 'last_save', code);
|
||||
code = getEditor().getCode()
|
||||
localStorage.setItem(path ?? 'last_save', code)
|
||||
}}
|
||||
class="h-full"
|
||||
automaticLayout={true}
|
||||
@@ -414,7 +414,7 @@
|
||||
<div
|
||||
class="flex flex-row w-full cursor-pointer h-full"
|
||||
on:click={() => {
|
||||
viewPreview = !viewPreview;
|
||||
viewPreview = !viewPreview
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row items-baseline">
|
||||
@@ -437,9 +437,9 @@
|
||||
? 'underline drop-shadow-md'
|
||||
: ''}"
|
||||
on:click|stopPropagation={() => {
|
||||
previewTab = 'input';
|
||||
viewPreview = true;
|
||||
inferSchema();
|
||||
previewTab = 'input'
|
||||
viewPreview = true
|
||||
inferSchema()
|
||||
}}
|
||||
>
|
||||
Inputs
|
||||
@@ -447,8 +447,8 @@
|
||||
<button
|
||||
class="font-semibold my-0 py-0 h-full ml-3 {previewTab === 'logs' ? 'underline' : ''}"
|
||||
on:click|stopPropagation={() => {
|
||||
previewTab = 'logs';
|
||||
viewPreview = true;
|
||||
previewTab = 'logs'
|
||||
viewPreview = true
|
||||
}}
|
||||
>
|
||||
Logs
|
||||
@@ -456,8 +456,8 @@
|
||||
<button
|
||||
class="font-semibold my-0 py-0 h-full ml-3 {previewTab === 'output' ? 'underline' : ''}"
|
||||
on:click|stopPropagation={() => {
|
||||
previewTab = 'output';
|
||||
viewPreview = true;
|
||||
previewTab = 'output'
|
||||
viewPreview = true
|
||||
}}
|
||||
>
|
||||
Result
|
||||
@@ -468,10 +468,10 @@
|
||||
: ''}"
|
||||
on:click|stopPropagation={() => {
|
||||
if (pastPreviews.length == 0) {
|
||||
loadPastPreviews();
|
||||
loadPastPreviews()
|
||||
}
|
||||
previewTab = 'history';
|
||||
viewPreview = true;
|
||||
previewTab = 'history'
|
||||
viewPreview = true
|
||||
}}
|
||||
>
|
||||
History
|
||||
@@ -481,8 +481,8 @@
|
||||
? 'underline'
|
||||
: ''}"
|
||||
on:click|stopPropagation={() => {
|
||||
previewTab = 'last_save';
|
||||
viewPreview = true;
|
||||
previewTab = 'last_save'
|
||||
viewPreview = true
|
||||
}}
|
||||
>
|
||||
Local save
|
||||
@@ -492,16 +492,16 @@
|
||||
<button
|
||||
class="mb-1 ml-2"
|
||||
on:click|stopPropagation={() => {
|
||||
viewPreview = !viewPreview;
|
||||
viewPreview = !viewPreview
|
||||
}}
|
||||
><Icon data={viewPreview ? faChevronDown : faChevronUp} scale={0.7} />
|
||||
</button>
|
||||
<button
|
||||
class="default-button py-px text-xs mx-2 align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
runPreview();
|
||||
viewPreview = true;
|
||||
previewTab = 'logs';
|
||||
runPreview()
|
||||
viewPreview = true
|
||||
previewTab = 'logs'
|
||||
}}
|
||||
>Run preview
|
||||
</button>
|
||||
@@ -559,9 +559,9 @@
|
||||
href="#last_save"
|
||||
class="text-xs"
|
||||
on:click={() => {
|
||||
modalViewerContent = lastSave;
|
||||
modalViewerMode = 'code';
|
||||
modalViewer.openModal();
|
||||
modalViewerContent = lastSave
|
||||
modalViewerMode = 'code'
|
||||
modalViewer.openModal()
|
||||
}}>View last local save for path {path}</a
|
||||
>
|
||||
{:else}No local save{/if}
|
||||
@@ -595,9 +595,9 @@
|
||||
href="#result"
|
||||
class="text-xs"
|
||||
on:click={() => {
|
||||
modalViewerContent = result;
|
||||
modalViewerMode = 'result';
|
||||
modalViewer.openModal();
|
||||
modalViewerContent = result
|
||||
modalViewerMode = 'result'
|
||||
modalViewer.openModal()
|
||||
}}>{JSON.stringify(result).substring(0, 30)}...</a
|
||||
></td
|
||||
>
|
||||
@@ -611,9 +611,9 @@
|
||||
workspace: $workspaceStore ?? 'NO_W',
|
||||
id
|
||||
})
|
||||
).raw_code;
|
||||
modalViewerMode = 'code';
|
||||
modalViewer.openModal();
|
||||
).raw_code
|
||||
modalViewerMode = 'code'
|
||||
modalViewer.openModal()
|
||||
}}
|
||||
>View code
|
||||
</a></td
|
||||
@@ -628,9 +628,9 @@
|
||||
workspace: $workspaceStore ?? 'NO_W',
|
||||
id
|
||||
})
|
||||
).logs;
|
||||
modalViewerMode = 'logs';
|
||||
modalViewer.openModal();
|
||||
).logs
|
||||
modalViewerMode = 'logs'
|
||||
modalViewer.openModal()
|
||||
}}
|
||||
>View logs
|
||||
</a></td
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '../../utils';
|
||||
import { ScriptService, FlowService } from '../../gen';
|
||||
import { sendUserToast } from '../../utils'
|
||||
import { ScriptService, FlowService } from '../../gen'
|
||||
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import ItemPicker from './ItemPicker.svelte';
|
||||
import RadioButton from './RadioButton.svelte';
|
||||
import Modal from './Modal.svelte';
|
||||
import { Highlight } from 'svelte-highlight';
|
||||
import { python } from 'svelte-highlight/src/languages';
|
||||
import github from 'svelte-highlight/src/styles/github';
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { workspaceStore } from '../../stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import Modal from './Modal.svelte'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { python } from 'svelte-highlight/src/languages'
|
||||
import github from 'svelte-highlight/src/styles/github'
|
||||
|
||||
export let scriptPath: string | undefined = undefined;
|
||||
export let allowFlow = false;
|
||||
export let isFlow = false;
|
||||
export let scriptPath: string | undefined = undefined
|
||||
export let allowFlow = false
|
||||
export let isFlow = false
|
||||
|
||||
let items: { summary: String; path: String; version?: String }[] = [];
|
||||
let itemPicker: ItemPicker;
|
||||
let modalViewer: Modal;
|
||||
let code: string = '';
|
||||
let items: { summary: String; path: String; version?: String }[] = []
|
||||
let itemPicker: ItemPicker
|
||||
let modalViewer: Modal
|
||||
let code: string = ''
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getScript() {
|
||||
code = (await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: scriptPath! }))
|
||||
.content;
|
||||
.content
|
||||
}
|
||||
|
||||
async function loadItems(isFlow: boolean): Promise<void> {
|
||||
try {
|
||||
if (isFlow) {
|
||||
items = await FlowService.listFlows({ workspace: $workspaceStore! });
|
||||
items = await FlowService.listFlows({ workspace: $workspaceStore! })
|
||||
} else {
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore! });
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore! })
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load items: ${err}`, true);
|
||||
sendUserToast(`Could not load items: ${err}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadItems(isFlow);
|
||||
loadItems(isFlow)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -55,12 +55,12 @@
|
||||
<ItemPicker
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
scriptPath = path;
|
||||
scriptPath = path
|
||||
}}
|
||||
itemName={isFlow ? 'Flow' : 'Script'}
|
||||
extraField="summary"
|
||||
loadItems={async () => {
|
||||
return items;
|
||||
return items
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<select
|
||||
bind:value={scriptPath}
|
||||
on:change={() => {
|
||||
dispatch('select', { path: scriptPath });
|
||||
dispatch('select', { path: scriptPath })
|
||||
}}
|
||||
class="max-w-lg"
|
||||
>
|
||||
@@ -93,8 +93,8 @@
|
||||
<button
|
||||
class="text-xs text-blue-500"
|
||||
on:click={async () => {
|
||||
await getScript();
|
||||
modalViewer.openModal();
|
||||
await getScript()
|
||||
modalViewer.openModal()
|
||||
}}>show code</button
|
||||
>
|
||||
{/if}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '../../common';
|
||||
import type { Schema } from '../../common'
|
||||
|
||||
import PageHeader from './PageHeader.svelte';
|
||||
import SchemaForm from './SchemaForm.svelte';
|
||||
import Tabs from './Tabs.svelte';
|
||||
import PageHeader from './PageHeader.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import Tabs from './Tabs.svelte'
|
||||
|
||||
import Highlight from 'svelte-highlight';
|
||||
import json from 'svelte-highlight/src/languages/json';
|
||||
import github from 'svelte-highlight/src/styles/github';
|
||||
import SvelteMarkdown from 'svelte-markdown';
|
||||
import Highlight from 'svelte-highlight'
|
||||
import json from 'svelte-highlight/src/languages/json'
|
||||
import github from 'svelte-highlight/src/styles/github'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
|
||||
export let schema: Schema;
|
||||
export let summary: string;
|
||||
export let description: string | undefined;
|
||||
export let synchronizedHeader = true;
|
||||
export let schema: Schema
|
||||
export let summary: string
|
||||
export let description: string | undefined
|
||||
export let synchronizedHeader = true
|
||||
|
||||
let tab: 'ui' | 'jsonschema' = 'ui';
|
||||
let tab: 'ui' | 'jsonschema' = 'ui'
|
||||
export function setSchema(newSchema: Schema) {
|
||||
schema = newSchema;
|
||||
schema = newSchema
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
<script lang="ts">
|
||||
import Modal from './Modal.svelte';
|
||||
import TableCustom from './TableCustom.svelte';
|
||||
import Modal from './Modal.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
|
||||
import { GranularAclService } from '../../gen/services/GranularAclService';
|
||||
import { sendUserToast } from '../../utils';
|
||||
import { GroupService, UserService } from '../../gen';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import AutoComplete from 'simple-svelte-autocomplete';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { GranularAclService } from '../../gen/services/GranularAclService'
|
||||
import { sendUserToast } from '../../utils'
|
||||
import { GroupService, UserService } from '../../gen'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import AutoComplete from 'simple-svelte-autocomplete'
|
||||
import { workspaceStore } from '../../stores'
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow';
|
||||
export let path: string = '';
|
||||
export let kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow'
|
||||
export let path: string = ''
|
||||
|
||||
let ownerKind: 'user' | 'group' = 'user';
|
||||
let owner: string = '';
|
||||
let ownerKind: 'user' | 'group' = 'user'
|
||||
let owner: string = ''
|
||||
|
||||
let newOwner: string = '';
|
||||
let write: boolean = false;
|
||||
let acls: [string, boolean][] = [];
|
||||
let groups: String[] = [];
|
||||
let usernames: string[] = [];
|
||||
let newOwner: string = ''
|
||||
let write: boolean = false
|
||||
let acls: [string, boolean][] = []
|
||||
let groups: String[] = []
|
||||
let usernames: string[] = []
|
||||
|
||||
let modal: Modal;
|
||||
let modal: Modal
|
||||
|
||||
$: newOwner = [ownerKind === 'group' ? 'g' : 'u', owner].join('/');
|
||||
$: newOwner = [ownerKind === 'group' ? 'g' : 'u', owner].join('/')
|
||||
|
||||
export async function openModal(newPath?: string) {
|
||||
if (newPath) {
|
||||
path = newPath;
|
||||
path = newPath
|
||||
}
|
||||
loadAcls();
|
||||
loadGroups();
|
||||
loadUsernames();
|
||||
modal.openModal();
|
||||
loadAcls()
|
||||
loadGroups()
|
||||
loadUsernames()
|
||||
modal.openModal()
|
||||
}
|
||||
|
||||
async function loadAcls() {
|
||||
acls = Object.entries(
|
||||
await GranularAclService.getGranularAcls({ workspace: $workspaceStore!, path, kind })
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
async function loadGroups(): Promise<void> {
|
||||
groups = await GroupService.listGroupNames({ workspace: $workspaceStore! });
|
||||
groups = await GroupService.listGroupNames({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
async function loadUsernames(): Promise<void> {
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
async function deleteAcl(owner: string) {
|
||||
@@ -58,11 +58,11 @@
|
||||
path,
|
||||
kind,
|
||||
requestBody: { owner }
|
||||
});
|
||||
loadAcls();
|
||||
dispatch('change');
|
||||
})
|
||||
loadAcls()
|
||||
dispatch('change')
|
||||
} catch (err) {
|
||||
sendUserToast(err.toString(), true);
|
||||
sendUserToast(err.toString(), true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +73,11 @@
|
||||
path,
|
||||
kind,
|
||||
requestBody: { owner, write }
|
||||
});
|
||||
loadAcls();
|
||||
dispatch('change');
|
||||
})
|
||||
loadAcls()
|
||||
dispatch('change')
|
||||
} catch (err) {
|
||||
sendUserToast(err.toString(), true);
|
||||
sendUserToast(err.toString(), true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -95,9 +95,9 @@
|
||||
bind:value={ownerKind}
|
||||
on:change={() => {
|
||||
if (ownerKind === 'group') {
|
||||
owner = 'all';
|
||||
owner = 'all'
|
||||
} else {
|
||||
owner = '';
|
||||
owner = ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { userStore } from '../../stores';
|
||||
import { userStore } from '../../stores'
|
||||
|
||||
import Badge from './Badge.svelte';
|
||||
export let extraPerms: Record<string, boolean> = {};
|
||||
export let canWrite: boolean;
|
||||
import Badge from './Badge.svelte'
|
||||
export let extraPerms: Record<string, boolean> = {}
|
||||
export let canWrite: boolean
|
||||
|
||||
let kind: 'read' | 'write' | undefined = undefined;
|
||||
let reason = '';
|
||||
let kind: 'read' | 'write' | undefined = undefined
|
||||
let reason = ''
|
||||
|
||||
$: {
|
||||
let username = $userStore?.username ?? '';
|
||||
let pgroups = $userStore?.pgroups ?? [];
|
||||
let pusername = `u/${username}`;
|
||||
let extraPermsKeys = Object.keys(extraPerms);
|
||||
let username = $userStore?.username ?? ''
|
||||
let pgroups = $userStore?.pgroups ?? []
|
||||
let pusername = `u/${username}`
|
||||
let extraPermsKeys = Object.keys(extraPerms)
|
||||
|
||||
if (pusername in extraPermsKeys) {
|
||||
if (extraPerms[pusername]) {
|
||||
kind = 'write';
|
||||
kind = 'write'
|
||||
} else {
|
||||
kind = 'read';
|
||||
kind = 'read'
|
||||
}
|
||||
reason = 'This item was shared to you personally';
|
||||
reason = 'This item was shared to you personally'
|
||||
} else {
|
||||
let writeGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x]);
|
||||
let writeGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x])
|
||||
if (pgroups.find((x) => x in extraPermsKeys && extraPerms[x])) {
|
||||
kind = 'write';
|
||||
reason = `This item was write shared to the group ${writeGroup} which you are a member of`;
|
||||
kind = 'write'
|
||||
reason = `This item was write shared to the group ${writeGroup} which you are a member of`
|
||||
} else {
|
||||
let readGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x]);
|
||||
let readGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x])
|
||||
if (pgroups.find((x) => extraPermsKeys.includes(x))) {
|
||||
kind = 'read';
|
||||
reason = `This item was read-only shared to the group ${readGroup} which you are a member of`;
|
||||
kind = 'read'
|
||||
reason = `This item was read-only shared to the group ${readGroup} which you are a member of`
|
||||
} else {
|
||||
kind = undefined;
|
||||
kind = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kind == 'read' && canWrite) {
|
||||
kind = undefined;
|
||||
kind = undefined
|
||||
}
|
||||
if (kind == undefined && !canWrite) {
|
||||
kind = 'read';
|
||||
reason = '';
|
||||
kind = 'read'
|
||||
reason = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user