Compare commits
160 Commits
main
...
signatures
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8db427309a | ||
|
|
47103527c4 | ||
|
|
5389e3b802 | ||
|
|
0162d69f66 | ||
|
|
3cefcdfc1e | ||
|
|
308eebd14b | ||
|
|
43d4e9b7c1 | ||
|
|
9ba962a42b | ||
|
|
f77de8447f | ||
|
|
a8380f8f80 | ||
|
|
ae289bdd92 | ||
|
|
c6d43a6165 | ||
|
|
0e7207d09d | ||
|
|
4181270800 | ||
|
|
dda92e84f6 | ||
|
|
ec467932f0 | ||
|
|
e414c7c6d0 | ||
|
|
5e7c284cda | ||
|
|
72a2583ad4 | ||
|
|
327c9c4ec9 | ||
|
|
4d126f83f0 | ||
|
|
5a7df29e1d | ||
|
|
0ebb8328ba | ||
|
|
cc2dc62c80 | ||
|
|
f2264a35f5 | ||
|
|
846f1e4ee8 | ||
|
|
85955eb83c | ||
|
|
38428028a8 | ||
|
|
ad6ca120e5 | ||
|
|
c5923e42e9 | ||
|
|
f5d67c0809 | ||
|
|
ca677c0bd0 | ||
|
|
93e51c025a | ||
|
|
923d6cea51 | ||
|
|
737bd152ea | ||
|
|
e6a47b2bc9 | ||
|
|
0317c17484 | ||
|
|
33c4eddbe8 | ||
|
|
3e0d43be70 | ||
|
|
4b9956d0a0 | ||
|
|
d1fcd03e62 | ||
|
|
0f328d4e50 | ||
|
|
9079b26b93 | ||
|
|
a80c18edaa | ||
|
|
1326c65a17 | ||
|
|
22ef2ec755 | ||
|
|
ca9ecce9b3 | ||
|
|
6c20e9b63c | ||
|
|
712d4f67ab | ||
|
|
93f1f7e9ca | ||
|
|
38d3f78566 | ||
|
|
6b3e134137 | ||
|
|
b8a5a90ebe | ||
|
|
359096c49a | ||
|
|
2b499f21f2 | ||
|
|
1ce1ec14d6 | ||
|
|
fc89027ec8 | ||
|
|
67fc77dec1 | ||
|
|
513de69113 | ||
|
|
5b68113e70 | ||
|
|
846332c72f | ||
|
|
a01f78cb4c | ||
|
|
456f64bd8c | ||
|
|
9601e210da | ||
|
|
2d295df413 | ||
|
|
1ca5ab4a63 | ||
|
|
26fe4732fb | ||
|
|
c57adaff70 | ||
|
|
4c95552569 | ||
|
|
91629fda39 | ||
|
|
9ff198d221 | ||
|
|
b22b75b506 | ||
|
|
39c880b615 | ||
|
|
7b77aaa570 | ||
|
|
8eaf03eb8b | ||
|
|
320546177c | ||
|
|
4c3491ca8c | ||
|
|
b85d16629f | ||
|
|
7624d5f05f | ||
|
|
5a5f5b2d4f | ||
|
|
5e95b2ce40 | ||
|
|
f096046a09 | ||
|
|
535030d400 | ||
|
|
e8b74dd980 | ||
|
|
636dee5f4b | ||
|
|
5feacbaec4 | ||
|
|
3e1d22bafd | ||
|
|
d6655fa871 | ||
|
|
e52f078555 | ||
|
|
fab1089a86 | ||
|
|
fa92648991 | ||
|
|
89bdbd32f8 | ||
|
|
d9028de9b9 | ||
|
|
d5a0b3ff83 | ||
|
|
421f9f113c | ||
|
|
c06cd34c4a | ||
|
|
1e2c8ef9b1 | ||
|
|
651ed0e735 | ||
|
|
632a25a2b6 | ||
|
|
df8f8ea022 | ||
|
|
1b0836051b | ||
|
|
76a83e08cb | ||
|
|
53ee8ec8eb | ||
|
|
33db63fcb5 | ||
|
|
ec2f33b70b | ||
|
|
be6b933ccb | ||
|
|
ea694f7d22 | ||
|
|
31b70d5c80 | ||
|
|
6b5d2e914f | ||
|
|
e53453e236 | ||
|
|
b8863ac021 | ||
|
|
51582f4c6e | ||
|
|
981d41f534 | ||
|
|
42bfc99373 | ||
|
|
c74da93ae7 | ||
|
|
2497e26b46 | ||
|
|
12a272c162 | ||
|
|
f2c59985b1 | ||
|
|
b69f23fca9 | ||
|
|
c5882fde5d | ||
|
|
26b4761ff8 | ||
|
|
13bbdb065d | ||
|
|
12c1f32d24 | ||
|
|
ca997bbdb7 | ||
|
|
fd8af8ad9e | ||
|
|
fa771d4678 | ||
|
|
2572ca61e8 | ||
|
|
7667405678 | ||
|
|
6b6938c546 | ||
|
|
00c267ac5d | ||
|
|
3107b5ff03 | ||
|
|
6e04f24990 | ||
|
|
d194f4633b | ||
|
|
f2218b3b18 | ||
|
|
8d59c29695 | ||
|
|
3a4efa8bd1 | ||
|
|
36147a785b | ||
|
|
ec3ec8f0fa | ||
|
|
ae8451b04a | ||
|
|
e4aca965d6 | ||
|
|
7f03ec7db6 | ||
|
|
ce42a91595 | ||
|
|
d03266b0a4 | ||
|
|
4a4eaa90e2 | ||
|
|
5e7c14b722 | ||
|
|
55b5695673 | ||
|
|
8596ac50b9 | ||
|
|
13fb52117b | ||
|
|
2c70a15594 | ||
|
|
7a51f842f0 | ||
|
|
a130806e19 | ||
|
|
fd1f05dd16 | ||
|
|
48e51733e0 | ||
|
|
e7817e6c9f | ||
|
|
51ad6edfcb | ||
|
|
315f7edd64 | ||
|
|
a2c3deab74 | ||
|
|
891b7eb93a | ||
|
|
7efd87be79 | ||
|
|
5acbc8b48c |
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rubenfiszel]
|
||||
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:
|
||||
|
||||
73
.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,51 @@ 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
|
||||
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: Docker meta
|
||||
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
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
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
@@ -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@v2
|
||||
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
@@ -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
@@ -0,0 +1,19 @@
|
||||
name: Publish python-client
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build_lsp:
|
||||
runs-on: [self-hosted, new]
|
||||
steps:
|
||||
- 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
|
||||
4
.github/workflows/release-please.yml
vendored
@@ -1,7 +1,7 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [main]
|
||||
|
||||
name: release-please
|
||||
jobs:
|
||||
release-please:
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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-server/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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
@@ -3243,7 +3309,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": {
|
||||
|
||||
@@ -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,20 @@ 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://localhost"
|
||||
- BASE_INTERNAL_URL=http://localhost:8000"
|
||||
- RUST_LOG=info
|
||||
- NUM_WORKERS=3
|
||||
- RUST_BACKTRACE=1
|
||||
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
@@ -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,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"}
|
||||
@@ -2,7 +2,14 @@
|
||||
import '../app.css';
|
||||
|
||||
import { OpenAPI, UserService, WorkspaceService } from '../gen';
|
||||
import { logout, clickOutside, sendUserToast, logoutWithRedirect, getUser } from '../utils';
|
||||
import {
|
||||
logout,
|
||||
clickOutside,
|
||||
sendUserToast,
|
||||
logoutWithRedirect,
|
||||
getUser,
|
||||
refreshSuperadmin
|
||||
} from '../utils';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import {
|
||||
@@ -73,15 +80,7 @@
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -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"
|
||||
|
||||
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>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { sendUserToast } from '../../utils';
|
||||
import Switch from '../components/Switch.svelte';
|
||||
|
||||
import Modal from './Modal.svelte';
|
||||
import type Modal from './Modal.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { workspaceStore } from '../../stores';
|
||||
import { WorkspaceService } from '../../gen';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { SettingsService } from '../../gen';
|
||||
|
||||
export let subtitle: string | undefined = undefined;
|
||||
export let title = 'Welcome to windmill.dev (alpha)';
|
||||
export let title = 'Welcome to Windmill (beta)';
|
||||
let version = '';
|
||||
|
||||
SettingsService.backendVersion().then((x) => {
|
||||
@@ -10,8 +10,8 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center h-screen bg-gray-50">
|
||||
<div class="w-10/12 md:w-7/12 lg:w-4/12 xl:3/12 m-auto">
|
||||
<div class="flex justify-center min-h-screen bg-gray-50 pt-10">
|
||||
<div class="w-10/12 md:w-7/12 lg:w-6/12 xl:4/12 m-auto">
|
||||
<h1 class="justify-center text-center font-medium pb-2 text-gray-700">
|
||||
{title}
|
||||
</h1>
|
||||
@@ -27,7 +27,7 @@
|
||||
<!-- empty row to make the form a little bit above vertical centering-->
|
||||
<div class="py-12" />
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 text-2xs text-gray-500 italic px-3 py-1">
|
||||
<div class="absolute top-0 right-0 text-2xs text-gray-500 italic px-3 py-1">
|
||||
windmill.dev backend v. <span class="font-mono">{version}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import { logout, sendUserToast } from '../../utils';
|
||||
import { logout, refreshSuperadmin, sendUserToast } from '../../utils';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { superadmin, userStore, workspaceStore } from '../../stores';
|
||||
import { UserService } from '../../gen';
|
||||
|
||||
// Default options
|
||||
const toastOptions = {
|
||||
@@ -51,15 +50,7 @@
|
||||
};
|
||||
workspaceStore.set(undefined);
|
||||
userStore.set(undefined);
|
||||
if ($superadmin == undefined) {
|
||||
UserService.globalWhoami().then((x) => {
|
||||
if (x.super_admin) {
|
||||
superadmin.set(x.email);
|
||||
} else {
|
||||
superadmin.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
refreshSuperadmin();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { UserService } from '../../gen';
|
||||
import { sendUserToast } from '../../utils';
|
||||
import { refreshSuperadmin, sendUserToast } from '../../utils';
|
||||
import { page } from '$app/stores';
|
||||
import { userStore, usersWorkspaceStore, workspaceStore } from '../../stores';
|
||||
import CenteredModal from './CenteredModal.svelte';
|
||||
@@ -26,6 +26,7 @@
|
||||
password
|
||||
}
|
||||
});
|
||||
refreshSuperadmin();
|
||||
if (rd) {
|
||||
goto(decodeURI(rd));
|
||||
} else {
|
||||
@@ -62,10 +63,10 @@
|
||||
|
||||
<!-- Enable submit form on enter -->
|
||||
<CenteredModal subtitle={error}>
|
||||
<div class="justify-center text-center">
|
||||
<div class="justify-center text-center flex flex-col">
|
||||
<span class="text-xs text-gray-600">Currently only signup through Github is supported</span>
|
||||
<a rel="external" href="/api/oauth/login/github"
|
||||
><button class="w-full default-button bg-black mt-2 py-2"
|
||||
><button class="m-auto default-button bg-black mt-2 py-2 w-96"
|
||||
>Signup or login with Github
|
||||
<Icon class="text-white pb-1" data={faGithub} scale={1.4} />
|
||||
</button></a
|
||||
@@ -73,7 +74,7 @@
|
||||
</div>
|
||||
<div class="flex flex-row-reverse w-full">
|
||||
<button
|
||||
class="my-6 text-xs text-blue-400"
|
||||
class="my-6 text-xs text-blue-400 m-auto"
|
||||
id="showPassword"
|
||||
on:click={() => {
|
||||
showPassword = !showPassword;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { userStore, workspaceStore } from '../stores';
|
||||
import { usersWorkspaceStore } from '../../stores';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import type { TruncatedToken, NewToken } from '../gen';
|
||||
import { UserService, SettingsService } from '../gen';
|
||||
import { displayDate, sendUserToast, getToday } from '../utils';
|
||||
import PageHeader from './components/PageHeader.svelte';
|
||||
import CenteredPage from './components/CenteredPage.svelte';
|
||||
import type { TruncatedToken, NewToken } from '../../gen';
|
||||
import { UserService, SettingsService } from '../../gen';
|
||||
import { displayDate, sendUserToast, getToday } from '../../utils';
|
||||
import PageHeader from './../components/PageHeader.svelte';
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import TableCustom from './components/TableCustom.svelte';
|
||||
import TableCustom from '../components/TableCustom.svelte';
|
||||
import CenteredModal from './CenteredModal.svelte';
|
||||
|
||||
let newPassword: string | undefined;
|
||||
let passwordError: string | undefined;
|
||||
@@ -21,14 +20,6 @@
|
||||
let displayCreateToken = false;
|
||||
let login_type = 'none';
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
const key = event.key || event.keyCode;
|
||||
if (key === 13 || key === 'Enter') {
|
||||
event.preventDefault();
|
||||
setPassword();
|
||||
}
|
||||
}
|
||||
|
||||
async function setPassword(): Promise<void> {
|
||||
try {
|
||||
if (newPassword) {
|
||||
@@ -90,19 +81,17 @@
|
||||
|
||||
loadVersion();
|
||||
loadLoginType();
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
listTokens();
|
||||
}
|
||||
}
|
||||
listTokens();
|
||||
</script>
|
||||
|
||||
<CenteredPage>
|
||||
<PageHeader title="User settings" />
|
||||
<CenteredModal title="User settings">
|
||||
<div class="flex flex-row justify-between">
|
||||
<a href="/user/workspaces">← Back to workspaces</a>
|
||||
</div>
|
||||
<div class="text-2xs text-gray-500 italic pb-6">
|
||||
Running windmill version (backend) {version}
|
||||
</div>
|
||||
<h2 class="border-b">Change password</h2>
|
||||
<h2 class="border-b">User info</h2>
|
||||
<div class="">
|
||||
{#if passwordError}
|
||||
<div class="text-purple-500 text-2xs grow">{passwordError}</div>
|
||||
@@ -110,10 +99,11 @@
|
||||
<div class="flex flex-col gap-2 w-full ">
|
||||
<div class="mt-4">
|
||||
<label class="block w-60 mb-2 text-gray-500">
|
||||
<input disabled value={$userStore?.email ?? ''} class="input mt-1" />
|
||||
<div class="text-gray-700">email</div>
|
||||
<input disabled value={$usersWorkspaceStore?.email} class="input mt-1" />
|
||||
</label>
|
||||
{#if login_type == 'password'}
|
||||
<label class="block w-12/12">
|
||||
<label class="block w-120">
|
||||
<div class="text-gray-700">password</div>
|
||||
<input
|
||||
type="password"
|
||||
@@ -131,6 +121,7 @@
|
||||
text-sm
|
||||
"
|
||||
/>
|
||||
<button on:click={() => setPassword()} class="mt-4 default-button">Set password</button>
|
||||
</label>
|
||||
{:else if login_type == 'github'}
|
||||
<span>Authentified through Github OAuth2. Cannot set a password.</span>
|
||||
@@ -243,7 +234,8 @@
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
</div>
|
||||
</CenteredPage>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<div class="flex flex-row justify-between pt-4">
|
||||
<a href="/user/workspaces">← Back to workspaces</a>
|
||||
</div>
|
||||
</CenteredModal>
|
||||
96
frontend/src/routes/user/superadmin_settings@user.svelte
Normal file
@@ -0,0 +1,96 @@
|
||||
<script lang="ts">
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import { superadmin, usersWorkspaceStore } from '../../stores';
|
||||
|
||||
import { UserService, SettingsService, GlobalUserInfo } from '../../gen';
|
||||
import { displayDate, sendUserToast, getToday } from '../../utils';
|
||||
import Icon from 'svelte-awesome';
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import TableCustom from '../components/TableCustom.svelte';
|
||||
import CenteredModal from './CenteredModal.svelte';
|
||||
import PageHeader from '../components/PageHeader.svelte';
|
||||
import InviteGlobalUser from '../components/InviteGlobalUser.svelte';
|
||||
|
||||
let version: string | undefined;
|
||||
let users: GlobalUserInfo[] = [];
|
||||
let filteredUsers: GlobalUserInfo[] | undefined;
|
||||
let userFilter = '';
|
||||
|
||||
const fuseOptions = {
|
||||
includeScore: false,
|
||||
keys: ['email', 'name', 'company']
|
||||
};
|
||||
|
||||
const fuse: Fuse<GlobalUserInfo> = new Fuse(users, fuseOptions);
|
||||
$: filteredUsers = fuse?.search(userFilter).map((value) => value.item);
|
||||
|
||||
async function loadVersion(): Promise<void> {
|
||||
version = await SettingsService.backendVersion();
|
||||
}
|
||||
|
||||
async function listUsers(): Promise<void> {
|
||||
users = await UserService.listUsersAsSuperAdmin({});
|
||||
fuse?.setCollection(users);
|
||||
}
|
||||
|
||||
loadVersion();
|
||||
listUsers();
|
||||
</script>
|
||||
|
||||
<CenteredModal title="Global Users Settings">
|
||||
<div class="flex flex-row justify-between">
|
||||
<a href="/user/workspaces">← Back to workspaces</a>
|
||||
</div>
|
||||
<div class="text-2xs text-gray-500 italic pb-6">
|
||||
Running windmill version (backend) {version}
|
||||
</div>
|
||||
|
||||
<PageHeader title="All users" primary={false} />
|
||||
<div class="pb-1" />
|
||||
<InviteGlobalUser on:new={listUsers} />
|
||||
<div class="pb-1" />
|
||||
|
||||
<input placeholder="Search users" bind:value={userFilter} class="input mt-1" />
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th>email</th>
|
||||
<th>superadmin</th>
|
||||
<th>login_type</th>
|
||||
<th>name</th>
|
||||
<th>company</th>
|
||||
<th />
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#if filteredUsers && users}
|
||||
{#each userFilter === '' ? users : filteredUsers as { email, super_admin, login_type, name, company }}
|
||||
<tr class="border">
|
||||
<td>{email}</td>
|
||||
<td>{super_admin}</td>
|
||||
<td>{login_type}</td>
|
||||
<td>{name}</td>
|
||||
<td>{company}</td>
|
||||
<td>
|
||||
<button
|
||||
class="text-blue-500"
|
||||
on:click={async () => {
|
||||
await UserService.globalUserUpdate({
|
||||
email,
|
||||
requestBody: {
|
||||
is_super_admin: !super_admin
|
||||
}
|
||||
});
|
||||
listUsers();
|
||||
}}>{super_admin ? 'demote' : 'promote'}</button
|
||||
></td
|
||||
>
|
||||
</tr>
|
||||
{/each}
|
||||
{/if}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
|
||||
<div class="flex flex-row justify-between pt-4">
|
||||
<a href="/user/workspaces">← Back to workspaces</a>
|
||||
</div>
|
||||
</CenteredModal>
|
||||
@@ -6,6 +6,8 @@
|
||||
import { superadmin, usersWorkspaceStore, workspaceStore } from '../../stores';
|
||||
import CenteredModal from './CenteredModal.svelte';
|
||||
import Switch from '../components/Switch.svelte';
|
||||
import { faCrown, faUser, faUserAlt, faUserCog } from '@fortawesome/free-solid-svg-icons';
|
||||
import Icon from 'svelte-awesome';
|
||||
|
||||
let invites: WorkspaceInvite[] = [];
|
||||
let list_all_as_super_admin: boolean = false;
|
||||
@@ -31,7 +33,7 @@
|
||||
}
|
||||
|
||||
async function loadWorkspacesAsAdmin() {
|
||||
workspaces = (await WorkspaceService.listWorkspacesAsSuperAdmin({})).map((x) => {
|
||||
workspaces = (await WorkspaceService.listWorkspacesAsSuperAdmin({ perPage: 1000 })).map((x) => {
|
||||
return { ...x, username: 'superadmin' };
|
||||
});
|
||||
}
|
||||
@@ -68,7 +70,8 @@
|
||||
<button
|
||||
class="
|
||||
block
|
||||
w-full
|
||||
w-96
|
||||
mx-auto
|
||||
py-1
|
||||
px-2
|
||||
rounded-md
|
||||
@@ -102,7 +105,8 @@
|
||||
<div
|
||||
class="
|
||||
block
|
||||
w-full
|
||||
w-96
|
||||
mx-auto
|
||||
py-1
|
||||
px-2
|
||||
rounded-md
|
||||
@@ -142,7 +146,15 @@
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex flex-row-reverse mt-10">
|
||||
<div class="flex justify-between mt-10">
|
||||
{#if $superadmin}
|
||||
<a class="mr-10" href="/user/superadmin_settings">
|
||||
<Icon data={faCrown} class="mr-1" scale={1} />Superadmin settings</a
|
||||
>
|
||||
{/if}
|
||||
<a class="mr-10" href="/user/settings">
|
||||
<Icon data={faUserCog} class="mr-1" scale={1} />User settings</a
|
||||
>
|
||||
<button
|
||||
class="default-button-secondary"
|
||||
on:click={async () => {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { writable, derived, readable } from 'svelte/store';
|
||||
export const userStore = writable(undefined);
|
||||
export const workspaceStore = writable(undefined);
|
||||
export const usersWorkspaceStore = writable(undefined);
|
||||
export const usernameStore = derived([usersWorkspaceStore, workspaceStore], ($values, set) => {
|
||||
set($values[0]?.workspaces.find(x => x.id == $values[1])?.username);
|
||||
});
|
||||
export const superadmin = writable(undefined);
|
||||
//# sourceMappingURL=stores.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"stores.js","sourceRoot":"","sources":["stores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAa1D,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAsB,SAAS,CAAC,CAAA;AACjE,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAqB,SAAS,CAAC,CAAA;AACrE,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAgC,SAAS,CAAC,CAAA;AACrF,MAAM,CAAC,MAAM,aAAa,GAAiC,OAAO,CAAC,CAAC,mBAAmB,EAAE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;IACvH,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;AACvE,CAAC,CAAC,CAAA;AACF,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAA6B,SAAS,CAAC,CAAA"}
|
||||
@@ -1,266 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { CancelablePromise, UserService } from './gen';
|
||||
import { superadmin, userStore, workspaceStore } from './stores';
|
||||
import { get } from 'svelte/store';
|
||||
import { goto } from '$app/navigation';
|
||||
export function isToday(someDate) {
|
||||
const today = new Date();
|
||||
return (someDate.getDate() == today.getDate() &&
|
||||
someDate.getMonth() == today.getMonth() &&
|
||||
someDate.getFullYear() == today.getFullYear());
|
||||
}
|
||||
export function daysAgo(someDate) {
|
||||
const today = new Date();
|
||||
return Math.floor((today.getTime() - someDate.getTime()) / 86400000);
|
||||
}
|
||||
export function secondsAgo(date) {
|
||||
return Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
||||
}
|
||||
export function displayDaysAgo(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const nbSecondsAgo = secondsAgo(date);
|
||||
if (nbSecondsAgo < 600) {
|
||||
return `${nbSecondsAgo}s ago`;
|
||||
}
|
||||
else if (isToday(date)) {
|
||||
return `today at ${date.toLocaleTimeString()}`;
|
||||
}
|
||||
else if (daysAgo(date) === 0) {
|
||||
return `${daysAgo(date) + 1} day ago`;
|
||||
}
|
||||
else {
|
||||
return `${daysAgo(date) + 1} day ago`;
|
||||
}
|
||||
}
|
||||
export function displayDate(dateString) {
|
||||
const date = new Date(dateString ?? '');
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return '';
|
||||
}
|
||||
else {
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} at ${date.toLocaleTimeString()}`;
|
||||
}
|
||||
}
|
||||
export function getToday() {
|
||||
var today = new Date();
|
||||
return today;
|
||||
}
|
||||
export function sendUserToast(message, error = false) {
|
||||
if (error) {
|
||||
toast.push(message, {
|
||||
theme: {
|
||||
'--toastBackground': '#FEE2E2',
|
||||
'--toastBarBackground': '#FEE2E2'
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
toast.push(message);
|
||||
}
|
||||
export function truncateHash(hash) {
|
||||
if (hash.length >= 6) {
|
||||
return hash.substr(hash.length - 6);
|
||||
}
|
||||
else {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
async function loadStore(workspace) {
|
||||
try {
|
||||
const user = await UserService.whoami({ workspace });
|
||||
const nuser = {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
created_at: user.created_at,
|
||||
is_admin: user.is_admin,
|
||||
groups: user.groups,
|
||||
pgroups: user.groups.map((x) => `g/${x}`)
|
||||
};
|
||||
userStore.set(nuser);
|
||||
return nuser;
|
||||
}
|
||||
catch (error) {
|
||||
userStore.set(undefined);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
export async function getUser(workspace) {
|
||||
const user = get(userStore);
|
||||
if (user === undefined) {
|
||||
return loadStore(workspace);
|
||||
}
|
||||
else {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
export function logoutWithRedirect(rd) {
|
||||
const error = encodeURIComponent('You have been logged out because your session has expired.');
|
||||
goto(`/user/login?error=${error}${rd ? '&rd=' + encodeURIComponent(rd) : ''}`);
|
||||
}
|
||||
export async function handle401(promise, rd) {
|
||||
// Redirects to login if the `promise` returns a 401 due to lack of authentication
|
||||
// Optionnally provide `rd`, to which the user will be redirected after logging back in
|
||||
return promise.catch(async (error) => {
|
||||
if (error.status === 401) {
|
||||
if (getUser(get(workspaceStore)) === undefined) {
|
||||
logoutWithRedirect(rd);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
throw Error('You do not have enough privilege to access this');
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
export function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
export function validatePassword(password) {
|
||||
const re = /^(?=.*[\d])(?=.*[!@#$%^&*])[\w!@#$%^&*]{8,30}$/;
|
||||
return re.test(password);
|
||||
}
|
||||
export async function logout(logoutMessage) {
|
||||
try {
|
||||
superadmin.set(undefined);
|
||||
goto(`/user/login${logoutMessage ? '?error=' + encodeURIComponent(logoutMessage) : ''}`);
|
||||
await UserService.logout();
|
||||
sendUserToast('you have been logged out');
|
||||
}
|
||||
catch (error) {
|
||||
goto(`/user/login?error=${encodeURIComponent('There was a problem logging you out, check the logs')}`);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(new CustomEvent('click_outside', node));
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleClick, true);
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
export function emptySchema() {
|
||||
return {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
properties: {},
|
||||
required: [],
|
||||
type: 'object'
|
||||
};
|
||||
}
|
||||
export function simpleSchema() {
|
||||
return {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
description: 'The name to hello world to',
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
required: []
|
||||
};
|
||||
}
|
||||
export function removeItemAll(arr, value) {
|
||||
var i = 0;
|
||||
while (i < arr.length) {
|
||||
if (arr[i] === value) {
|
||||
arr.splice(i, 1);
|
||||
}
|
||||
else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
export function canWrite(path, extra_perms, user) {
|
||||
let keys = Object.keys(extra_perms);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
if (user.is_admin) {
|
||||
return true;
|
||||
}
|
||||
let userOwner = `u/${user.username}`;
|
||||
if (path.startsWith(userOwner)) {
|
||||
return true;
|
||||
}
|
||||
if (keys.includes(userOwner) && extra_perms[userOwner]) {
|
||||
return true;
|
||||
}
|
||||
if (user.pgroups.findIndex((x) => path.startsWith(x) || (keys.includes(x) && extra_perms[x])) != -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function removeKeysWithEmptyValues(obj) {
|
||||
Object.keys(obj).forEach((key) => (obj[key] === undefined ? delete obj[key] : {}));
|
||||
}
|
||||
export function allTrue(dict) {
|
||||
for (let v of Object.values(dict)) {
|
||||
if (!v)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
export function forLater(scheduledString) {
|
||||
return new Date() < new Date(scheduledString);
|
||||
}
|
||||
export function elapsedSinceSecs(date) {
|
||||
return Math.round((new Date().getTime() - new Date(date).getTime()) / 1000);
|
||||
}
|
||||
export function groupBy(scripts, toGroup, dflts = []) {
|
||||
let r = {};
|
||||
for (const dflt of dflts) {
|
||||
r[dflt] = [];
|
||||
}
|
||||
scripts.forEach((sc) => {
|
||||
let section = toGroup(sc);
|
||||
if (section in r) {
|
||||
r[section].push(sc);
|
||||
}
|
||||
else {
|
||||
r[section] = [sc];
|
||||
}
|
||||
});
|
||||
return Object.entries(r).sort((s1, s2) => {
|
||||
let n1 = s1[0];
|
||||
let n2 = s2[0];
|
||||
if (n1 > n2) {
|
||||
return 1;
|
||||
}
|
||||
else if (n1 < n2) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
export function truncate(s, n, suffix = '...') {
|
||||
if (s.length <= n) {
|
||||
return s;
|
||||
}
|
||||
else {
|
||||
return s.substring(0, n) + suffix;
|
||||
}
|
||||
}
|
||||
export function truncateRev(s, n, prefix = '...') {
|
||||
if (s.length <= n) {
|
||||
return s;
|
||||
}
|
||||
else {
|
||||
return prefix + s.substring(s.length - n, s.length);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=utils.js.map
|
||||
@@ -136,6 +136,18 @@ export function validatePassword(password: string): boolean {
|
||||
return re.test(password)
|
||||
}
|
||||
|
||||
export async function refreshSuperadmin(): Promise<void> {
|
||||
if (get(superadmin) == undefined) {
|
||||
UserService.globalWhoami().then((x) => {
|
||||
if (x.super_admin) {
|
||||
superadmin.set(x.email)
|
||||
} else {
|
||||
superadmin.set(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function logout(logoutMessage?: string): Promise<void> {
|
||||
try {
|
||||
superadmin.set(undefined)
|
||||
|
||||
4
imgs/architecture.svg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
imgs/step1.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
imgs/step2.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
imgs/step3.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
23
init-db.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
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$;
|
||||