Compare commits
1 Commits
rf/flatten
...
rf/pkce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5099bcb215 |
2
.github/workflows/backend-test.yml
vendored
2
.github/workflows/backend-test.yml
vendored
@@ -40,4 +40,4 @@ jobs:
|
||||
backend -> target
|
||||
- name: cargo test
|
||||
timeout-minutes: 10
|
||||
run: mkdir frontend/build && cd backend && touch windmill-api/openapi-deref.yaml && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill DISABLE_NSJAIL=false cargo test --all -- --nocapture
|
||||
run: mkdir frontend/build && cd backend && touch windmill-api/openapi-deref.yaml && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill cargo test --all -- --nocapture
|
||||
|
||||
1
.github/workflows/change-versions.yml
vendored
1
.github/workflows/change-versions.yml
vendored
@@ -10,7 +10,6 @@ jobs:
|
||||
container: node:18
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: git config --system --add safe.directory /__w/windmill/windmill
|
||||
- name: Change versions
|
||||
run: ./.github/change-versions.sh "$(cat version.txt)"
|
||||
- uses: actions-rs/toolchain@v1
|
||||
|
||||
131
CHANGELOG.md
131
CHANGELOG.md
@@ -1,137 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.69.0](https://github.com/windmill-labs/windmill/compare/v1.68.0...v1.69.0) (2023-02-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** Duplicate component ([#1228](https://github.com/windmill-labs/windmill/issues/1228)) ([089a6b6](https://github.com/windmill-labs/windmill/commit/089a6b6ae52e8d28dd15e2f9a6ad900c5853d0a1))
|
||||
* **frontend:** Properly delete tab content ([#1227](https://github.com/windmill-labs/windmill/issues/1227)) ([857ee5f](https://github.com/windmill-labs/windmill/commit/857ee5f318466d12bf0d41515451798df087ab74))
|
||||
* **frontend:** Support deeply nested components ([#1225](https://github.com/windmill-labs/windmill/issues/1225)) ([6ad876e](https://github.com/windmill-labs/windmill/commit/6ad876ebb45a934b7a4dc980cf38a5228d7d11f1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** .wmillignore whitelist behavior ([d543650](https://github.com/windmill-labs/windmill/commit/d543650b313c434e794ad800aefe4aeda83c0fed))
|
||||
|
||||
## [1.68.0](https://github.com/windmill-labs/windmill/compare/v1.67.4...v1.68.0) (2023-02-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** Add more app component CSS customisation ([#1218](https://github.com/windmill-labs/windmill/issues/1218)) ([6044e3b](https://github.com/windmill-labs/windmill/commit/6044e3b6ef92e89b8f15f38bc2d0986ec64105d5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** better ergonomics around workspace add ([40c12e6](https://github.com/windmill-labs/windmill/commit/40c12e6139c7b42d7ab169bab2dd37f8b43bea06))
|
||||
* **cli:** better ergonomics around workspaces ([3b7160e](https://github.com/windmill-labs/windmill/commit/3b7160e84aa454bdb5f343da99cfd97a6b319937))
|
||||
|
||||
## [1.67.4](https://github.com/windmill-labs/windmill/compare/v1.67.3...v1.67.4) (2023-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** workflow check for has_failure_module ([e54dc3f](https://github.com/windmill-labs/windmill/commit/e54dc3ff97e4454a15b9efe25cc12f6c9e1e176b))
|
||||
|
||||
## [1.67.3](https://github.com/windmill-labs/windmill/compare/v1.67.2...v1.67.3) (2023-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** ignone non wmill looking files ([ec57c59](https://github.com/windmill-labs/windmill/commit/ec57c5977f122b629a07e05bc3551662d518ce30))
|
||||
|
||||
## [1.67.2](https://github.com/windmill-labs/windmill/compare/v1.67.1...v1.67.2) (2023-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** ignone non wmill looking files ([969e89f](https://github.com/windmill-labs/windmill/commit/969e89f8bbc10f6712920321b70ede35f19ab9ed))
|
||||
|
||||
## [1.67.1](https://github.com/windmill-labs/windmill/compare/v1.67.0...v1.67.1) (2023-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** coloring nits ([3fa24ad](https://github.com/windmill-labs/windmill/commit/3fa24adad0a07ba2f469c545b28251b035efdf90))
|
||||
|
||||
## [1.67.0](https://github.com/windmill-labs/windmill/compare/v1.66.1...v1.67.0) (2023-02-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** Add app sub grids ([#1208](https://github.com/windmill-labs/windmill/issues/1208)) ([dbc59e9](https://github.com/windmill-labs/windmill/commit/dbc59e952143ee5813780ad13794cef4e036911c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** add --fail-conflicts to ci push ([0085b46](https://github.com/windmill-labs/windmill/commit/0085b46c1e3b8267fcafcb06ce72b4d820e49df5))
|
||||
|
||||
## [1.66.1](https://github.com/windmill-labs/windmill/compare/v1.66.0...v1.66.1) (2023-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** delete workspace instead of archiving them ([70dfc8b](https://github.com/windmill-labs/windmill/commit/70dfc8b8d0293d80da7db14caa1b9eb0ed67653d))
|
||||
|
||||
## [1.66.0](https://github.com/windmill-labs/windmill/compare/v1.65.0...v1.66.0) (2023-02-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add delete flows ([e81f7bd](https://github.com/windmill-labs/windmill/commit/e81f7bd7239b73710da2a4ddec0da7805c13da06))
|
||||
* CLI refactor v1 ([e31d2ae](https://github.com/windmill-labs/windmill/commit/e31d2ae27f886e774ffc429eea80057f4f9f4213))
|
||||
* **frontend:** Add image app component ([#1213](https://github.com/windmill-labs/windmill/issues/1213)) ([a4b773a](https://github.com/windmill-labs/windmill/commit/a4b773af294554c5787f02ebda363c8d9a3eff1b))
|
||||
|
||||
## [1.65.0](https://github.com/windmill-labs/windmill/compare/v1.64.0...v1.65.0) (2023-02-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **apps:** add asJson for customcss ([71d6dad](https://github.com/windmill-labs/windmill/commit/71d6dad37cc239952ce7799609c02474b0b1fc81))
|
||||
* **apps:** add custom css for apps ([7f00e1c](https://github.com/windmill-labs/windmill/commit/7f00e1c1a8f2e905b0677d82ba547f55dc23b3e0))
|
||||
* **backend:** Zip Workspace Export ([#1201](https://github.com/windmill-labs/windmill/issues/1201)) ([5d109b3](https://github.com/windmill-labs/windmill/commit/5d109b3cd4b7749788f9cb9fcbe1949c45eedf1f))
|
||||
* **frontend:** Add divider app component ([#1209](https://github.com/windmill-labs/windmill/issues/1209)) ([c33e79e](https://github.com/windmill-labs/windmill/commit/c33e79e0b8d5ba1103d87fdd47fcd0e1071e19de))
|
||||
* **frontend:** Add file input app component ([#1211](https://github.com/windmill-labs/windmill/issues/1211)) ([d4b6d69](https://github.com/windmill-labs/windmill/commit/d4b6d691264bf21e4e2c97548aaad9aa80678a6b))
|
||||
* **frontend:** Add icon app component ([#1207](https://github.com/windmill-labs/windmill/issues/1207)) ([e4791c2](https://github.com/windmill-labs/windmill/commit/e4791c2b7e3a0e6b90c37bc1200f9cd0ab3b6845))
|
||||
|
||||
## [1.64.0](https://github.com/windmill-labs/windmill/compare/v1.63.2...v1.64.0) (2023-02-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** Trigger settings drawer with URL hash ([#1185](https://github.com/windmill-labs/windmill/issues/1185)) ([8445697](https://github.com/windmill-labs/windmill/commit/8445697e31394ac11f3b8aa10af1546cc9c0041c))
|
||||
|
||||
## [1.63.2](https://github.com/windmill-labs/windmill/compare/v1.63.1...v1.63.2) (2023-02-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **psql:** update pg client ([a2fbc57](https://github.com/windmill-labs/windmill/commit/a2fbc5702509bb259bae106baa9a6146360ec5dd))
|
||||
|
||||
## [1.63.1](https://github.com/windmill-labs/windmill/compare/v1.63.0...v1.63.1) (2023-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update hub sync script ([03eb144](https://github.com/windmill-labs/windmill/commit/03eb1444c4a5dfbd170ba8d200784e530ca2f771))
|
||||
|
||||
## [1.63.0](https://github.com/windmill-labs/windmill/compare/v1.62.0...v1.63.0) (2023-02-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add mem peak info ([f584062](https://github.com/windmill-labs/windmill/commit/f584062f13aa7da8e767fd35de1aef7bbb67c3c8))
|
||||
* **frontend:** Minimal support for custom filenames ([#1190](https://github.com/windmill-labs/windmill/issues/1190)) ([b03b3be](https://github.com/windmill-labs/windmill/commit/b03b3be154efb0984f9623c27acc05617f125bc5))
|
||||
* **worker:** set oom_adj to 1000 to prioritize killing subprocess ([265fbc5](https://github.com/windmill-labs/windmill/commit/265fbc5835d029d510a794e171392884cb20bdae))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **python:** return none if argument is missing ([3f2754b](https://github.com/windmill-labs/windmill/commit/3f2754b3305f6cb65373d532ff0db6020bf07e45))
|
||||
* Update references to the docs ([#1191](https://github.com/windmill-labs/windmill/issues/1191)) ([a574270](https://github.com/windmill-labs/windmill/commit/a574270bc259f423c984259cd7d9a6d91b77815c))
|
||||
|
||||
## [1.62.0](https://github.com/windmill-labs/windmill/compare/v1.61.1...v1.62.0) (2023-02-03)
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ COPY .git/ .git/
|
||||
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$features"
|
||||
|
||||
|
||||
FROM python:3.11.2-slim-buster
|
||||
FROM python:3.11.1-slim-buster
|
||||
|
||||
ARG APP=/usr/src/app
|
||||
|
||||
|
||||
124
README.md
124
README.md
@@ -5,7 +5,7 @@
|
||||
<em>.</em>
|
||||
</p>
|
||||
<p align=center>
|
||||
Open-source developer infrastructure for internal tools. Self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs to trigger workflows and scripts as internal apps. Scripts are turned into UIs and no-code modules, no-code modules can be composed into very rich flows, and script and flows can be triggered from internal UIs made with a low-code builder. The script languages supported are: Python, Typescript, Go, Bash, SQL.
|
||||
Open-source developer infrastructure for internal tools. Self-hostable alternative to Airplane, Pipedream, Superblocks and a simplified Temporal with autogenerated UIs to trigger workflows and scripts as internal apps. Scripts are turned into UIs and no-code modules, no-code modules can be composed into very rich flows, and script and flows can be triggered from internal UIs made with a low-code builder. The script languages supported are: Python, Typescript, Go, Bash.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -20,35 +20,55 @@ Open-source developer infrastructure for internal tools. Self-hostable alternati
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
**Try it (personal workspaces are free forever)**: <https://app.windmill.dev>
|
||||
|
||||
**Documentation**: <https://docs.windmill.dev>
|
||||
|
||||
**Discord**: <https://discord.gg/V7PM2YHsPB>
|
||||
|
||||
**Hub**: <https://hub.windmill.dev>
|
||||
|
||||
**Contributor's guide**: <https://docs.windmill.dev/docs/misc/contributing>
|
||||
|
||||
**Roadmap**: <https://github.com/orgs/windmill-labs/projects/2>
|
||||
|
||||
You can show your support for the project by starring this repo.
|
||||
|
||||
Windmill Labs offers commercial licenses, an enterprise edition, local hub
|
||||
mirrors, and support: contact ruben@windmill.dev.
|
||||
|
||||
---
|
||||
|
||||
# Windmill
|
||||
|
||||
<p align="center">
|
||||
<a href="https://app.windmill.dev">Try it</a> - <a href="https://docs.windmill.dev/docs/intro/">Docs</a> - <a href="https://discord.gg/V7PM2YHsPB">Discord</a> - <a href="https://hub.windmill.dev">Hub</a> - <a href="https://docs.windmill.dev/docs/misc/contributing">Contributor's guide</a>
|
||||
<b>Disclaimer: </b>Windmill is in <b>BETA</b>. It is secure to run in production but we are still <a href="https://github.com/orgs/windmill-labs/projects/2">improving the product fast<a/>.
|
||||
</p>
|
||||
|
||||
# Windmill - Turn scripts into workflows and UIs that you can share and run at scale
|
||||

|
||||

|
||||
|
||||
Windmill is <b>fully open-sourced (AGPLv3)</b> and Windmill Labs offers dedicated instance and commercial support and licenses.
|
||||
Windmill is <b>fully open-sourced (AGPLv3)</b>:
|
||||
|
||||

|
||||
|
||||
https://user-images.githubusercontent.com/275584/218350457-bc2fdc3b-e667-4da5-a2bd-3bacc1f0ec79.mp4
|
||||
|
||||
- [Windmill - Turn scripts into workflows and UIs that you can share and run at scale](#windmill---turn-scripts-into-workflows-and-uis-that-you-can-share-and-run-at-scale)
|
||||
- [Windmill](#windmill)
|
||||
- [Main Concepts](#main-concepts)
|
||||
- [Show me some actual script code](#show-me-some-actual-script-code)
|
||||
- [CLI](#cli)
|
||||
- [Running scripts locally](#running-scripts-locally)
|
||||
- [Layout](#layout)
|
||||
- [Stack](#stack)
|
||||
- [Security](#security)
|
||||
- [Sandboxing](#sandboxing)
|
||||
- [Sandboxing and workload isolation](#sandboxing-and-workload-isolation)
|
||||
- [Secrets, credentials and sensitive values](#secrets-credentials-and-sensitive-values)
|
||||
- [Performance](#performance)
|
||||
- [Architecture](#architecture)
|
||||
- [Big-picture Architecture](#big-picture-architecture)
|
||||
- [Technical Architecture](#technical-architecture)
|
||||
- [How to self-host](#how-to-self-host)
|
||||
- [Docker compose](#docker-compose)
|
||||
- [Kubernetes (k8s) and Helm charts](#kubernetes-k8s-and-helm-charts)
|
||||
- [Postgres without superuser](#postgres-without-superuser)
|
||||
- [Commercial license](#commercial-license)
|
||||
- [OAuth for self-hosting](#oauth-for-self-hosting)
|
||||
- [OAuth for self-hosting (very optional)](#oauth-for-self-hosting-very-optional)
|
||||
- [Resource types](#resource-types)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Run a local dev setup](#run-a-local-dev-setup)
|
||||
@@ -79,49 +99,20 @@ through webhooks.
|
||||
|
||||
You can build your entire infra on top of Windmill!
|
||||
|
||||
## Show me some actual script code
|
||||
|
||||
```typescript
|
||||
import * as wmill from "https://deno.land/x/windmill@v1.62.0/mod.ts"
|
||||
//import any dependency from npm
|
||||
|
||||
import cowsay from 'npm:cowsay@1.5.0'
|
||||
|
||||
export async function main(
|
||||
a: number,
|
||||
// unions generate enums
|
||||
b: "my" | "enum",
|
||||
// default parameters prefill the field
|
||||
d = "default arg",
|
||||
// nested objects work c = { nested: "object" },
|
||||
// permissioned and typed json
|
||||
db: wmill.Resource<"postgresql">) {
|
||||
|
||||
const email = Deno.env.get('WM_EMAIL')
|
||||
// variables are permissioned and by path
|
||||
let variable = await wmill.getVariable('f/company-folder/my_secret')
|
||||
const lastTimeRun = await wmill.getState()
|
||||
// logs are printed and always inspectable
|
||||
console.log(cowsay.say({ text: "hello " + email + " " + lastTimeRun }))
|
||||
await wmill.setState(Date.now())
|
||||
|
||||
// return is serialized as JSON
|
||||
return { foo: d, variable };
|
||||
}
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
We have a powerful CLI to interact with the windmill platform and sync your
|
||||
scripts from local files, github repos and to run scripts and flows on the instance from local commands. See
|
||||
scripts from your own github repo. See
|
||||
[more details](https://github.com/windmill-labs/windmill/tree/main/cli)
|
||||
|
||||

|
||||
|
||||
## Layout
|
||||
|
||||
### Running scripts locally
|
||||
|
||||
You can run your script locally easily, you simply need to pass the right environment variables for the `wmill` client library to fetch resource and variables from your instance if necessary. See more: <https://docs.windmill.dev/docs/advanced/local_development/>
|
||||
- `backend/`: Rust backend
|
||||
- `frontend`: Svelte frontend
|
||||
- `lsp/`: Lsp asssistant for the monaco editor
|
||||
- `<lang>-client/`: Windmill client for the given `<lang>`
|
||||
|
||||
## Stack
|
||||
|
||||
@@ -144,7 +135,7 @@ You can run your script locally easily, you simply need to pass the right enviro
|
||||
|
||||
## Security
|
||||
|
||||
### Sandboxing
|
||||
### Sandboxing and workload isolation
|
||||
|
||||
Windmill uses [nsjail](https://github.com/google/nsjail) on top of the deno
|
||||
sandboxing. It is production multi-tenant grade secure. Do not take our word for
|
||||
@@ -170,8 +161,14 @@ back to the database is ~50ms. A typical lightweight deno job will take around
|
||||
|
||||
<p align="center">
|
||||
|
||||
### Big-picture Architecture
|
||||
|
||||
<img src="./imgs/diagram.svg">
|
||||
|
||||
### Technical Architecture
|
||||
|
||||
<img src="./imgs/architecture.svg">
|
||||
|
||||
</p>
|
||||
|
||||
## How to self-host
|
||||
@@ -187,6 +184,10 @@ compiling from source or using without a postgres super user, see
|
||||
|
||||
Go to http://localhost et voilà :)
|
||||
|
||||
For older kernels < 4.18, set `DISABLE_NUSER=true` as env variable, otherwise
|
||||
nsjail will not be able to launch the isolated scripts.
|
||||
|
||||
To disable nsjail altogether, set `DISABLE_NSJAIL=true`.
|
||||
|
||||
The default super-admin user is: admin@windmill.dev / changeme
|
||||
|
||||
@@ -197,12 +198,6 @@ From there, you can create other users (do not forget to change the password!)
|
||||
We publish helm charts at:
|
||||
<https://github.com/windmill-labs/windmill-helm-charts>
|
||||
|
||||
### Postgres without superuser
|
||||
|
||||
If you do not want, or cannot (for instance, in AWS Aurora or Cloud sql) use a postgres superuser,
|
||||
you can run `./init-db-as-superuser.sql` to init the required users for windmill.
|
||||
|
||||
|
||||
### Commercial license
|
||||
|
||||
To self-host Windmill, you must respect the terms of the AGPLv3 license which
|
||||
@@ -213,14 +208,14 @@ comfortable with AGPLv3.
|
||||
To re-expose any Windmill parts to your users as a feature of your product, or
|
||||
to build a feature on top of Windmill, to comply with AGPLv3 your product must
|
||||
be AGPLv3 or you must get a commercial license. Contact us at
|
||||
<ruben@windmill.dev> if you have any doubts.
|
||||
<license@windmill.dev> if you have any doubts.
|
||||
|
||||
In addition, a commercial license grants you a dedicated engineer to transition
|
||||
your current infrastructure to Windmill, support with tight SLA, audit logs
|
||||
export features, SSO, unlimited users creation, advanced permission managing
|
||||
features such as groups and the ability to create more than one workspace.
|
||||
|
||||
### OAuth for self-hosting
|
||||
### OAuth for self-hosting (very optional)
|
||||
|
||||
To get the same oauth integrations as Windmill Cloud, mount `oauth.json` with
|
||||
the following format:
|
||||
@@ -275,16 +270,16 @@ You may also add your own custom OAuth2 IdP and OAuth2 Resource provider:
|
||||
### Resource types
|
||||
|
||||
You will also want to import all the approved resource types from
|
||||
[WindmillHub](https://hub.windmill.dev). A setup script will prompt
|
||||
you to have it being synced automatically everyday.
|
||||
[WindmillHub](https://hub.windmill.dev). There is no automatic way to do this
|
||||
automatically currently, but it will be possible using a command with the
|
||||
upcoming CLI tool.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Environment Variable name | Default | Description | Api Server/Worker/All |
|
||||
| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| DATABASE_URL | | The Postgres database url. | All |
|
||||
| DISABLE_NSJAIL | true | Disable Nsjail Sandboxing | Worker |
|
||||
| PORT | 8000 | Exposed port | Server | |
|
||||
| DISABLE_NSJAIL | true | Disable Nsjail Sandboxing | |
|
||||
| NUM_WORKERS | 3 | The number of worker per Worker instance (set to 1 on Eks to have 1 pod = 1 worker, set to 0 for an API only instance) | Worker |
|
||||
| DISABLE_SERVER | false | Binary would operate as a worker only instance | Worker |
|
||||
| METRICS_ADDR | None | The socket addr at which to expose Prometheus metrics at the /metrics path. Set to "true" to expose it on port 8001 | All |
|
||||
@@ -300,7 +295,8 @@ you to have it being synced automatically everyday.
|
||||
| S3_CACHE_BUCKET (EE only) | None | The S3 bucket to sync the cache of the workers to | Worker |
|
||||
| TAR_CACHE_RATE (EE only) | 100 | The rate at which to tar the cache of the workers. 100 means every 100th job in average (uniformly randomly distributed). | Worker |
|
||||
| SLACK_SIGNING_SECRET | None | The signing secret of your Slack app. See [Slack documentation](https://api.slack.com/authentication/verifying-requests-from-slack) | Server |
|
||||
| COOKIE_DOMAIN | None | The domain of the cookie. If not set, the cookie will be set by the browser based on the full origin | Server | |
|
||||
| COOKIE_DOMAIN | None | The domain of the cookie. If not set, the cookie will be set by the browser based on the full origin | Server |
|
||||
| SERVE_CSP | None | The CSP directives to use when serving the frontend static assets | Server |
|
||||
| DENO_PATH | /usr/bin/deno | The path to the deno binary. | Worker |
|
||||
| PYTHON_PATH | /usr/local/bin/python3 | The path to the python binary. | Worker |
|
||||
| GO_PATH | /usr/bin/go | The path to the go binary. | Worker |
|
||||
@@ -367,4 +363,4 @@ running options.
|
||||
|
||||
## Copyright
|
||||
|
||||
Windmill Labs, Inc 2023
|
||||
Windmill Labs, Inc 2022
|
||||
|
||||
387
backend/Cargo.lock
generated
387
backend/Cargo.lock
generated
@@ -12,12 +12,6 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@@ -61,9 +55,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.69"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
@@ -110,23 +104,6 @@ dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
|
||||
dependencies = [
|
||||
"bzip2",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"xz2",
|
||||
"zstd",
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.6.0"
|
||||
@@ -212,21 +189,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async_zip"
|
||||
version = "0.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50d29ab7e2f9e808cca1a69ea56a36f4ff216f54a41a23aae1fd4afc05cc020"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"chrono",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"pin-project",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "1.0.0"
|
||||
@@ -255,9 +217,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.7"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
|
||||
checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -424,9 +386,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.6.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96f9cdd34d6eb553f9ea20e5bf84abb7b13c729f113fc1d8e49dc00ad9fa8738"
|
||||
checksum = "5b9c056b9ed43aee5e064b683aa1ec783e19c6acec7559e3ae931b7490472fbe"
|
||||
dependencies = [
|
||||
"cargo-lock",
|
||||
"git2",
|
||||
@@ -450,27 +412,6 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-lock"
|
||||
version = "8.0.3"
|
||||
@@ -525,9 +466,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.6"
|
||||
version = "4.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
|
||||
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
@@ -614,12 +555,12 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.17.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.19",
|
||||
"time 0.3.17",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@@ -672,15 +613,6 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cron"
|
||||
version = "0.12.0"
|
||||
@@ -729,9 +661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.91"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
|
||||
checksum = "bc831ee6a32dd495436e317595e639a587aa9907bef96fe6e6abc290ab6204e9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
@@ -741,9 +673,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.91"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
|
||||
checksum = "94331d54f1b1a8895cd81049f7eaaaef9d05a7dcb4d1fd08bf3ff0806246789d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
@@ -756,15 +688,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.91"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
|
||||
checksum = "48dcd35ba14ca9b40d6e4b4b39961f23d835dbb8eed74565ded361d93e1feb8a"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.91"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
|
||||
checksum = "81bbeb29798b407ccd82a3324ade1a7286e0d29851475990b612670f6f5124d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1044,23 +976,23 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.20"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
|
||||
checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1069,16 +1001,6 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1311,9 +1233,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.16.1"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc"
|
||||
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
@@ -1411,12 +1333,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -1452,9 +1368,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.9"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -1638,9 +1554,9 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
|
||||
|
||||
[[package]]
|
||||
name = "is-macro"
|
||||
version = "0.2.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20"
|
||||
checksum = "1c068d4c6b922cd6284c609cfa6dec0e41615c9c5a1a4ba729a970d8daba05fb"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"pmutil",
|
||||
@@ -1651,14 +1567,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.4"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
|
||||
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"hermit-abi 0.2.6",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1902,17 +1818,6 @@ dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-sys"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magic-crypt"
|
||||
version = "3.1.12"
|
||||
@@ -1993,25 +1898,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.6"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2149,18 +2045,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.10"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0072973714303aa6e3631c7e8e777970cf4bdd25dc4932e41031027b8bcc4e"
|
||||
checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.10"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0629cbd6b897944899b1f10496d9c4a7ac5878d45fd61bc22e9e79bfbbc29597"
|
||||
checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -2170,9 +2066,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -2336,9 +2232,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.5"
|
||||
version = "2.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660"
|
||||
checksum = "4ab62d2fa33726dbe6321cc97ef96d8cde531e3eeaf858a058de53a8a6d40d8f"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
@@ -2346,9 +2242,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.3"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
|
||||
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
@@ -2556,9 +2452,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2566,7 +2462,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#88ff3cf1e056a8c3901f17e73ae0d1fba991a17f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"built",
|
||||
@@ -2584,7 +2480,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#88ff3cf1e056a8c3901f17e73ae0d1fba991a17f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2598,7 +2494,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#88ff3cf1e056a8c3901f17e73ae0d1fba991a17f"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"heck",
|
||||
@@ -2620,7 +2516,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#88ff3cf1e056a8c3901f17e73ae0d1fba991a17f"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
@@ -2654,15 +2550,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
@@ -2967,16 +2854,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.8"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3003,7 +2890,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
|
||||
source = "git+https://github.com/RustPython/RustPython#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -3012,7 +2899,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
|
||||
source = "git+https://github.com/RustPython/RustPython#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -3029,7 +2916,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
|
||||
source = "git+https://github.com/RustPython/RustPython#adc23253e4b58980b407ba2760dbe61681d752fc"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -3222,9 +3109,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.9"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294"
|
||||
checksum = "718dc5fff5b36f99093fc49b280cfc96ce6fc824317783bff5a1fed0c7a64819"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3253,9 +3140,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.93"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@@ -3390,9 +3277,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -3415,9 +3302,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.8"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
|
||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@@ -3441,9 +3328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.24"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
|
||||
checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3615,19 +3502,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "stacker"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"psm",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -3697,9 +3571,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "swc_atoms"
|
||||
version = "0.4.37"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f88175a66f5a7c189e752bda520e148317776ecb22c75adc2c2f24c490834bd0"
|
||||
checksum = "731cf66bd8e11030f056f91f9d8af77f83ec4377ff04d1670778a57d1607402a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
@@ -3711,9 +3585,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_common"
|
||||
version = "0.29.32"
|
||||
version = "0.29.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc8e0e8109b26be70c82d9709562fc88cbcc09e03c2458221cf216c0088dea2"
|
||||
checksum = "a97e491d31418cd33fea58e9f893316fc04b30e2b5d0e750c066e2ba4907ae54"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ast_node",
|
||||
@@ -3738,9 +3612,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.96.7"
|
||||
version = "0.96.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "621c66e27fbb6cbb6434a4e2b25e439e9a2583cc3419a4a83eba51d16ac0cd7b"
|
||||
checksum = "a887102d5595b557261aa4bde35f3d71906fba674d4b79cd5c59b4155b12ee2d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"is-macro",
|
||||
@@ -3755,9 +3629,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.124.12"
|
||||
version = "0.124.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b3f472b3dfbfd279de364d2b014459a281824b938e243a8739037c445d6b6c"
|
||||
checksum = "9e75888eabf1ad8a8968e3befc7cd20c10e4721254d3344285bd5c3a42f58dc1"
|
||||
dependencies = [
|
||||
"either",
|
||||
"enum_kind",
|
||||
@@ -3765,7 +3639,6 @@ dependencies = [
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"stacker",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
@@ -3823,9 +3696,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.108"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3894,11 +3767,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.7"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
@@ -3926,9 +3798,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.19"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2"
|
||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
@@ -3944,9 +3816,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.7"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c"
|
||||
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
@@ -4020,9 +3892,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
@@ -4041,9 +3913,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.12"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
|
||||
checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -4067,9 +3939,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.7"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
||||
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -4136,9 +4008,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-cookies"
|
||||
version = "0.9.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40f38d941a2ffd8402b36e02ae407637a9caceb693aaf2edc910437db0f36984"
|
||||
checksum = "4efe6d9c78aae53650a340d0e702e4f103582dce6af6bf897dc1b56c8ed5d4f6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -4300,7 +4172,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
|
||||
source = "git+https://github.com/oxidecomputer/typify#77a43da6b1e9a79fe3ec719a038f4ea949c0342e"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
@@ -4309,7 +4181,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
|
||||
source = "git+https://github.com/oxidecomputer/typify#77a43da6b1e9a79fe3ec719a038f4ea949c0342e"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
@@ -4327,7 +4199,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
|
||||
source = "git+https://github.com/oxidecomputer/typify#77a43da6b1e9a79fe3ec719a038f4ea949c0342e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4766,7 +4638,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4793,13 +4665,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"async-oauth2",
|
||||
"async-stripe",
|
||||
"async_zip",
|
||||
"axum",
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4826,7 +4697,7 @@ dependencies = [
|
||||
"sql-builder",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"time 0.3.19",
|
||||
"time 0.3.17",
|
||||
"tokio",
|
||||
"tokio-tar",
|
||||
"tokio-util",
|
||||
@@ -4848,7 +4719,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4863,7 +4734,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4876,7 +4747,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4884,7 +4755,6 @@ dependencies = [
|
||||
"hex",
|
||||
"hmac",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
"prometheus",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
@@ -4901,7 +4771,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4909,7 +4779,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4923,7 +4793,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4935,7 +4805,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4950,7 +4820,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4964,7 +4834,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4987,7 +4857,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -5119,47 +4989,8 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xz2"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||
dependencies = [
|
||||
"lzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "5.0.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.7+zstd.1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.69.0"
|
||||
version = "1.62.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -28,11 +28,7 @@ name = "windmill"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[features]
|
||||
enterprise = [
|
||||
"windmill-worker/enterprise",
|
||||
"windmill-queue/enterprise",
|
||||
"windmill-api/enterprise",
|
||||
]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -87,7 +83,7 @@ chrono = { version = "^0", features = ["serde"] }
|
||||
tracing = "^0"
|
||||
tracing-subscriber = { version = "^0", features = ["env-filter", "json"] }
|
||||
prometheus = { version = "^0", default-features = false }
|
||||
cookie = { version = "0.17.0" }
|
||||
cookie = { version = "0.16.2" }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
rust-embed = "^6"
|
||||
mime_guess = "^2"
|
||||
@@ -125,7 +121,7 @@ async-recursion = "^1"
|
||||
swc_common = "^0"
|
||||
swc_ecma_parser = "^0"
|
||||
swc_ecma_ast = "^0"
|
||||
base64 = "0.21.0"
|
||||
base64 = "^0"
|
||||
unicode-general-category = "^0"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.6"
|
||||
@@ -148,8 +144,4 @@ serde_derive = "1.0.147"
|
||||
const_format = { version = "0.2", features = ["rust_1_64", "rust_1_51"] }
|
||||
dyn-iter = "0.2.0"
|
||||
rsa = "0.7.2"
|
||||
async-stripe = { version = "0.14", features = [
|
||||
"runtime-tokio-hyper",
|
||||
"checkout",
|
||||
] }
|
||||
async_zip = { version = "0.0.11", features = ["full"] }
|
||||
async-stripe = { version = "0.14", features = ["runtime-tokio-hyper", "checkout"] }
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,12 +0,0 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE
|
||||
password
|
||||
ADD
|
||||
first_time_user boolean NOT NULL DEFAULT (false);
|
||||
|
||||
UPDATE
|
||||
password
|
||||
SET
|
||||
first_time_user = true
|
||||
WHERE
|
||||
email = 'admin@windmill.dev';
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,16 +0,0 @@
|
||||
-- Add up migration script here
|
||||
-- Add up migration script here
|
||||
UPDATE script SET content = 'import wmill from "https://deno.land/x/wmill@v1.63.1/main.ts";
|
||||
export async function main() {
|
||||
await run(
|
||||
"workspace", "add", "__automation", "admins", Deno.env.get("BASE_INTERNAL_URL") + "/", "--token", Deno.env.get("WM_TOKEN"));
|
||||
|
||||
await run("hub", "pull");
|
||||
}
|
||||
|
||||
async function run(...cmd: string[]) {
|
||||
console.log("Running \"" + cmd.join('' '') + "\"");
|
||||
await wmill.parse(cmd);
|
||||
}', summary = 'Synchronize Hub Resource types with admins workspace',
|
||||
description = 'Basic administrative script to sync latest resource types from hub to share to every workspace. Recommended to run at least once. On a schedule by default.'
|
||||
WHERE hash = -28028598712388162 AND workspace_id = 'admins';
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,8 +17,10 @@ use serde_json::json;
|
||||
use windmill_common::error;
|
||||
use windmill_parser::{json_to_typ, Arg, MainArgSignature, Typ};
|
||||
|
||||
use rustpython_parser as parser;
|
||||
use rustpython_parser::ast::{Constant, ExprKind, Located, StmtKind};
|
||||
use rustpython_parser::{
|
||||
ast::{Constant, ExprKind, Located, StmtKind},
|
||||
parser,
|
||||
};
|
||||
|
||||
const DEF_MAIN: &str = "def main(";
|
||||
const FUNCTION_CALL: &str = "<function call>";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,9 @@ use std::net::SocketAddr;
|
||||
use git_version::git_version;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::utils::rd_string;
|
||||
use windmill_worker::WorkerConfig;
|
||||
|
||||
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
const DEFAULT_NUM_WORKERS: usize = 3;
|
||||
const DEFAULT_PORT: u16 = 8000;
|
||||
|
||||
mod ee;
|
||||
|
||||
@@ -27,7 +26,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let num_workers = std::env::var("NUM_WORKERS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
.unwrap_or(DEFAULT_NUM_WORKERS as i32);
|
||||
.unwrap_or(windmill_common::DEFAULT_NUM_WORKERS as i32);
|
||||
|
||||
let metrics_addr: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
.ok()
|
||||
@@ -39,13 +38,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
let port: u16 = std::env::var("PORT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u16>().ok())
|
||||
.unwrap_or(DEFAULT_PORT as u16);
|
||||
let base_internal_url: String = std::env::var("BASE_INTERNAL_URL")
|
||||
.unwrap_or_else(|_| format!("http://localhost:{}", port.to_string()));
|
||||
|
||||
let server_mode = !std::env::var("DISABLE_SERVER")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
@@ -60,25 +52,58 @@ async fn main() -> anyhow::Result<()> {
|
||||
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
let shutdown_signal = windmill_common::shutdown_signal(tx);
|
||||
|
||||
if server_mode || num_workers > 0 {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
|
||||
let base_internal_url =
|
||||
std::env::var("BASE_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
|
||||
let timeout = std::env::var("TIMEOUT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
.unwrap_or(windmill_common::DEFAULT_TIMEOUT);
|
||||
|
||||
if server_mode || num_workers > 0 {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 8000));
|
||||
|
||||
let base_url2 = base_url.clone();
|
||||
let server_f = async {
|
||||
if server_mode {
|
||||
windmill_api::run_server(db.clone(), addr, rx.resubscribe()).await?;
|
||||
windmill_api::run_server(db.clone(), addr, base_url, rx.resubscribe()).await?;
|
||||
}
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
|
||||
let base_url = base_url2.clone();
|
||||
let workers_f = async {
|
||||
if num_workers > 0 {
|
||||
let sleep_queue = std::env::var("SLEEP_QUEUE")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
.unwrap_or(windmill_common::DEFAULT_SLEEP_QUEUE);
|
||||
let disable_nuser = std::env::var("DISABLE_NUSER")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
let disable_nsjail = std::env::var("DISABLE_NSJAIL")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(true);
|
||||
let keep_job_dir = std::env::var("KEEP_JOB_DIR")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
let license_key = std::env::var("LICENSE_KEY").ok();
|
||||
let sync_bucket = std::env::var("S3_CACHE_BUCKET")
|
||||
.ok()
|
||||
.map(|e| Some(e))
|
||||
.unwrap_or(None);
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
tracing::info!(
|
||||
"
|
||||
"
|
||||
##############################
|
||||
Windmill Enterprise Edition {GIT_VERSION}
|
||||
Windmill Enterprise Edition {GIT_VERSION} LICENSE_KEY: {license_key:?}, S3_CACHE_BUCKET: {sync_bucket:?}
|
||||
##############################"
|
||||
);
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
tracing::info!(
|
||||
@@ -88,58 +113,38 @@ Windmill Community Edition {GIT_VERSION}
|
||||
##############################"
|
||||
);
|
||||
|
||||
display_config(vec![
|
||||
"DISABLE_NSJAIL",
|
||||
"DISABLE_SERVER",
|
||||
"NUM_WORKERS",
|
||||
"METRICS_ADDR",
|
||||
"JSON_FMT",
|
||||
"BASE_URL",
|
||||
"BASE_INTERNAL_URL",
|
||||
"TIMEOUT",
|
||||
"SLEEP_QUEUE",
|
||||
"MAX_LOG_SIZE",
|
||||
"PORT",
|
||||
"KEEP_JOB_DIR",
|
||||
"S3_CACHE_BUCKET",
|
||||
"TAR_CACHE_RATE",
|
||||
"COOKIE_DOMAIN",
|
||||
"PYTHON_PATH",
|
||||
"DENO_PATH",
|
||||
"GO_PATH",
|
||||
"PIP_INDEX_URL",
|
||||
"PIP_EXTRA_INDEX_URL",
|
||||
"PIP_TRUSTED_HOST",
|
||||
"PATH",
|
||||
"HOME",
|
||||
"DATABASE_CONNECTIONS",
|
||||
"TIMEOUT_WAIT_RESULT",
|
||||
"QUEUE_LIMIT_WAIT_RESULT",
|
||||
"DENO_AUTH_TOKENS",
|
||||
"DENO_FLAGS",
|
||||
"PIP_LOCAL_DEPENDENCIES",
|
||||
"ADDITIONAL_PYTHON_PATHS",
|
||||
"INCLUDE_HEADERS",
|
||||
"WHITELIST_WORKSPACES",
|
||||
"BLACKLIST_WORKSPACES",
|
||||
"NEW_USER_WEBHOOK",
|
||||
"CLOUD_HOSTED",
|
||||
]);
|
||||
tracing::info!(
|
||||
"DISABLE_NSJAIL: {disable_nsjail}, DISABLE_NUSER: {disable_nuser}, BASE_URL: \
|
||||
{base_url}, SLEEP_QUEUE: {sleep_queue}, NUM_WORKERS: {num_workers}, TIMEOUT: \
|
||||
{timeout}, KEEP_JOB_DIR: {keep_job_dir}"
|
||||
);
|
||||
|
||||
run_workers(
|
||||
db.clone(),
|
||||
rx.resubscribe(),
|
||||
addr,
|
||||
timeout,
|
||||
num_workers,
|
||||
base_internal_url.clone(),
|
||||
sleep_queue,
|
||||
WorkerConfig {
|
||||
disable_nsjail,
|
||||
disable_nuser,
|
||||
base_internal_url,
|
||||
base_url,
|
||||
keep_job_dir,
|
||||
},
|
||||
rx.resubscribe(),
|
||||
sync_bucket,
|
||||
license_key,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
|
||||
let base_url = base_url2;
|
||||
let monitor_f = async {
|
||||
if server_mode {
|
||||
monitor_db(&db, rx.resubscribe(), &base_internal_url);
|
||||
monitor_db(&db, timeout, base_url, rx.resubscribe());
|
||||
}
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
@@ -158,46 +163,34 @@ Windmill Community Edition {GIT_VERSION}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_config(envs: Vec<&str>) {
|
||||
tracing::info!(
|
||||
"config: {}",
|
||||
envs.iter()
|
||||
.filter(|env| std::env::var(env).is_ok())
|
||||
.map(|env| {
|
||||
format!(
|
||||
"{}: {}",
|
||||
env,
|
||||
std::env::var(env).unwrap_or_else(|_| "not set".to_string())
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
pub fn monitor_db(
|
||||
db: &Pool<Postgres>,
|
||||
timeout: i32,
|
||||
base_url: String,
|
||||
rx: tokio::sync::broadcast::Receiver<()>,
|
||||
base_internal_url: &str,
|
||||
) {
|
||||
let db1 = db.clone();
|
||||
let db2 = db.clone();
|
||||
|
||||
let rx2 = rx.resubscribe();
|
||||
let base_internal_url = base_internal_url.to_string();
|
||||
|
||||
tokio::spawn(async move {
|
||||
windmill_worker::handle_zombie_jobs_periodically(&db1, rx, &base_internal_url).await
|
||||
windmill_worker::handle_zombie_jobs_periodically(&db1, timeout, &base_url, rx).await
|
||||
});
|
||||
tokio::spawn(async move { windmill_api::delete_expired_items_perdiodically(&db2, rx2).await });
|
||||
}
|
||||
|
||||
pub async fn run_workers(
|
||||
db: Pool<Postgres>,
|
||||
rx: tokio::sync::broadcast::Receiver<()>,
|
||||
addr: SocketAddr,
|
||||
timeout: i32,
|
||||
num_workers: i32,
|
||||
base_internal_url: String,
|
||||
sleep_queue: u64,
|
||||
worker_config: WorkerConfig,
|
||||
rx: tokio::sync::broadcast::Receiver<()>,
|
||||
mut periodic_script: Option<String>,
|
||||
license_key: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let license_key = std::env::var("LICENSE_KEY").ok();
|
||||
#[cfg(feature = "enterprise")]
|
||||
ee::verify_license_key(license_key)?;
|
||||
|
||||
@@ -205,6 +198,12 @@ pub async fn run_workers(
|
||||
if license_key.is_some() {
|
||||
panic!("License key is required ONLY for the enterprise edition");
|
||||
}
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
if !worker_config.disable_nsjail {
|
||||
tracing::warn!(
|
||||
"NSJAIL to sandbox process in untrusted environments is an enterprise feature but allowed to be used for testing purposes"
|
||||
);
|
||||
}
|
||||
|
||||
let instance_name = rd_string(5);
|
||||
let monitor = tokio_metrics::TaskMonitor::new();
|
||||
@@ -224,17 +223,22 @@ pub async fn run_workers(
|
||||
let worker_name = format!("dt-worker-{}-{}", &instance_name, rd_string(5));
|
||||
let ip = ip.clone();
|
||||
let rx = rx.resubscribe();
|
||||
let base_internal_url = base_internal_url.clone();
|
||||
let worker_config = worker_config.clone();
|
||||
let wp = periodic_script.take();
|
||||
handles.push(tokio::spawn(monitor.instrument(async move {
|
||||
tracing::info!(worker = %worker_name, "starting worker");
|
||||
tracing::info!(addr = %addr.to_string(), worker = %worker_name, "starting worker");
|
||||
windmill_worker::run_worker(
|
||||
&db1,
|
||||
timeout,
|
||||
&instance_name,
|
||||
worker_name,
|
||||
i as u64,
|
||||
num_workers as u64,
|
||||
&ip,
|
||||
sleep_queue,
|
||||
worker_config,
|
||||
wp,
|
||||
rx,
|
||||
&base_internal_url,
|
||||
)
|
||||
.await
|
||||
})));
|
||||
|
||||
@@ -6,8 +6,10 @@ use windmill_common::{
|
||||
flow_status::{FlowStatus, FlowStatusModule},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue, InputTransform},
|
||||
scripts::ScriptLang,
|
||||
DEFAULT_SLEEP_QUEUE,
|
||||
};
|
||||
use windmill_queue::{get_queued_job, JobPayload, RawCode};
|
||||
use windmill_worker::WorkerConfig;
|
||||
|
||||
async fn initialize_tracing() {
|
||||
use std::sync::Once;
|
||||
@@ -87,7 +89,14 @@ impl ApiServer {
|
||||
let addr = sock.local_addr().unwrap();
|
||||
drop(sock);
|
||||
|
||||
let task = tokio::task::spawn(windmill_api::run_server(db.clone(), addr, rx));
|
||||
let task = tokio::task::spawn({
|
||||
windmill_api::run_server(
|
||||
db.clone(),
|
||||
addr,
|
||||
format!("http://localhost:{}", addr.port()),
|
||||
rx,
|
||||
)
|
||||
});
|
||||
|
||||
return Self { addr, tx, task };
|
||||
}
|
||||
@@ -908,20 +917,43 @@ fn spawn_test_worker(
|
||||
) {
|
||||
let (tx, rx) = tokio::sync::broadcast::channel(1);
|
||||
let db = db.to_owned();
|
||||
let timeout = 4_000;
|
||||
let worker_instance: &str = "test worker instance";
|
||||
let worker_name: String = next_worker_name();
|
||||
let i_worker: u64 = Default::default();
|
||||
let num_workers: u64 = 2;
|
||||
let ip: &str = Default::default();
|
||||
let sleep_queue: u64 = DEFAULT_SLEEP_QUEUE / num_workers;
|
||||
let port = port;
|
||||
let worker_config = WorkerConfig {
|
||||
base_internal_url: format!("http://localhost:{port}"),
|
||||
base_url: format!("http://localhost:{port}"),
|
||||
disable_nuser: std::env::var("DISABLE_NUSER")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false),
|
||||
disable_nsjail: std::env::var("DISABLE_NSJAIL")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false),
|
||||
keep_job_dir: std::env::var("KEEP_JOB_DIR")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let future = async move {
|
||||
let base_internal_url = format!("http://localhost:{}", port);
|
||||
windmill_worker::run_worker(
|
||||
&db,
|
||||
timeout,
|
||||
worker_instance,
|
||||
worker_name,
|
||||
i_worker,
|
||||
num_workers,
|
||||
ip,
|
||||
sleep_queue,
|
||||
worker_config,
|
||||
None,
|
||||
rx,
|
||||
&base_internal_url,
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
@@ -8,8 +8,12 @@ edition.workspace = true
|
||||
name = "windmill_api"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "windmill_api"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
enterprise = ["windmill-queue/enterprise", "async-stripe"]
|
||||
enterprise = ["windmill-queue/enterprise"]
|
||||
|
||||
[dependencies]
|
||||
windmill-queue.workspace = true
|
||||
@@ -65,7 +69,6 @@ hmac.workspace = true
|
||||
cookie.workspace = true
|
||||
sha2.workspace = true
|
||||
urlencoding.workspace = true
|
||||
async-stripe = { workspace = true, optional = true }
|
||||
async-stripe.workspace = true
|
||||
lazy_static.workspace = true
|
||||
prometheus.workspace = true
|
||||
async_zip.workspace = true
|
||||
prometheus.workspace = true
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.69.0
|
||||
version: 1.62.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -1092,10 +1092,6 @@ paths:
|
||||
- variable
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: already_encrypted
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
requestBody:
|
||||
description: new variable
|
||||
required: true
|
||||
@@ -1137,10 +1133,6 @@ paths:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
- name: already_encrypted
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
requestBody:
|
||||
description: updated variable
|
||||
required: true
|
||||
@@ -2716,23 +2708,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/flows/delete/{path}:
|
||||
delete:
|
||||
summary: delete flow by path
|
||||
operationId: deleteFlowByPath
|
||||
tags:
|
||||
- flow
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/ScriptPath"
|
||||
responses:
|
||||
"200":
|
||||
description: flow delete
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/apps/list:
|
||||
get:
|
||||
summary: list all available apps
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::{
|
||||
users::{require_owner_of_path, Authed, OptAuthed},
|
||||
variables::build_crypt,
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Json, Path, Query},
|
||||
@@ -22,6 +21,7 @@ use axum::{
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -170,7 +170,7 @@ async fn list_apps(
|
||||
)
|
||||
.order_desc("favorite.path IS NOT NULL")
|
||||
.order_by("app_version.created_at", true)
|
||||
.and_where("app.workspace_id = ?".bind(&w_id))
|
||||
.and_where("app.workspace_id = ? OR app.workspace_id = 'starter'".bind(&w_id))
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.clone();
|
||||
@@ -368,9 +368,12 @@ async fn create_app(
|
||||
Ok((StatusCode::CREATED, app.path))
|
||||
}
|
||||
|
||||
async fn list_hub_apps(Authed { email, .. }: Authed) -> JsonResult<serde_json::Value> {
|
||||
async fn list_hub_apps(
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let flows = list_elems_from_hub(
|
||||
&HTTP_CLIENT,
|
||||
http_client,
|
||||
"https://hub.windmill.dev/searchUiData?approved=true",
|
||||
&email,
|
||||
)
|
||||
@@ -381,9 +384,10 @@ async fn list_hub_apps(Authed { email, .. }: Authed) -> JsonResult<serde_json::V
|
||||
pub async fn get_hub_app_by_id(
|
||||
Authed { email, .. }: Authed,
|
||||
Path(id): Path<i32>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let value = http_get_from_hub(
|
||||
&HTTP_CLIENT,
|
||||
http_client,
|
||||
&format!("https://hub.windmill.dev/apps/{id}/json"),
|
||||
&email,
|
||||
false,
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
*/
|
||||
|
||||
use hyper::StatusCode;
|
||||
use reqwest::Client;
|
||||
use sql_builder::prelude::*;
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{delete, get, post},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use sql_builder::SqlBuilder;
|
||||
@@ -32,7 +33,6 @@ use crate::{
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -41,7 +41,6 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/create", post(create_flow))
|
||||
.route("/update/*path", post(update_flow))
|
||||
.route("/archive/*path", post(archive_flow_by_path))
|
||||
.route("/delete/*path", delete(delete_flow_by_path))
|
||||
.route("/get/*path", get(get_flow_by_path))
|
||||
.route("/exists/*path", get(exists_flow_by_path))
|
||||
.route("/list_paths", get(list_paths))
|
||||
@@ -82,7 +81,7 @@ async fn list_flows(
|
||||
)
|
||||
.order_desc("favorite.path IS NOT NULL")
|
||||
.order_by("edited_at", lq.order_desc.unwrap_or(true))
|
||||
.and_where("o.workspace_id = ?".bind(&w_id))
|
||||
.and_where("o.workspace_id = ? OR o.workspace_id = 'starter'".bind(&w_id))
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.clone();
|
||||
@@ -112,9 +111,12 @@ async fn list_flows(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
async fn list_hub_flows(Authed { email, .. }: Authed) -> JsonResult<serde_json::Value> {
|
||||
async fn list_hub_flows(
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let flows = list_elems_from_hub(
|
||||
&HTTP_CLIENT,
|
||||
http_client,
|
||||
"https://hub.windmill.dev/searchFlowData?approved=true",
|
||||
&email,
|
||||
)
|
||||
@@ -143,9 +145,10 @@ async fn list_paths(
|
||||
pub async fn get_hub_flow_by_id(
|
||||
Authed { email, .. }: Authed,
|
||||
Path(id): Path<i32>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let value = http_get_from_hub(
|
||||
&HTTP_CLIENT,
|
||||
http_client,
|
||||
&format!("https://hub.windmill.dev/flows/{id}/json"),
|
||||
&email,
|
||||
false,
|
||||
@@ -427,12 +430,13 @@ async fn get_flow_by_path(
|
||||
let path = path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let flow_o =
|
||||
sqlx::query_as::<_, Flow>("SELECT * FROM flow WHERE path = $1 AND workspace_id = $2")
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
let flow_o = sqlx::query_as::<_, Flow>(
|
||||
"SELECT * FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
let flow = not_found_if_none(flow_o, "Flow", path)?;
|
||||
@@ -446,7 +450,8 @@ async fn exists_flow_by_path(
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM flow WHERE path = $1 AND workspace_id = $2)",
|
||||
"SELECT EXISTS(SELECT 1 FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id \
|
||||
= 'starter'))",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
@@ -493,42 +498,6 @@ async fn archive_flow_by_path(
|
||||
Ok(format!("Flow {path} archived"))
|
||||
}
|
||||
|
||||
async fn delete_flow_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
) -> Result<String> {
|
||||
let path = path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
"flows.delete",
|
||||
ActionKind::Delete,
|
||||
&w_id,
|
||||
Some(path),
|
||||
Some([("workspace", w_id.as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteFlow { workspace: w_id, path: path.to_owned() },
|
||||
);
|
||||
|
||||
Ok(format!("Flow {path} deleted"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
extract::{FromRequest, Json, Path, Query},
|
||||
@@ -36,7 +38,7 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::{require_owner_of_path, Authed},
|
||||
variables::get_workspace_key,
|
||||
BASE_URL,
|
||||
BaseUrl, QueueLimitWaitResult, TimeoutWaitResult,
|
||||
};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -152,7 +154,8 @@ pub async fn get_path_for_hash<'c>(
|
||||
hash: i64,
|
||||
) -> error::Result<String> {
|
||||
let path = sqlx::query_scalar!(
|
||||
"select path from script where hash = $1 AND workspace_id = $2",
|
||||
"select path from script where hash = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter')",
|
||||
hash,
|
||||
w_id
|
||||
)
|
||||
@@ -922,16 +925,16 @@ pub async fn get_resume_urls(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Path((w_id, job_id, resume_id)): Path<(String, Uuid, u32)>,
|
||||
Query(approver): Query<QueryApprover>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
) -> error::JsonResult<ResumeUrls> {
|
||||
let key = get_workspace_key(&w_id, &mut user_db.begin(&authed).await?).await?;
|
||||
let signature = create_signature(key, job_id, resume_id, approver.approver.clone())?;
|
||||
let base_url = base_url.0.clone();
|
||||
let approver = approver
|
||||
.approver
|
||||
.as_ref()
|
||||
.map(|x| format!("?approver={}", encode(x)))
|
||||
.unwrap_or_else(String::new);
|
||||
|
||||
let base_url = BASE_URL.as_str();
|
||||
let res = ResumeUrls {
|
||||
approvalPage: format!(
|
||||
"{base_url}/approve/{w_id}/{job_id}/{resume_id}/{signature}{approver}"
|
||||
@@ -1312,26 +1315,18 @@ pub async fn check_queue_too_long(db: DB, queue_limit: Option<i64>) -> error::Re
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref QUEUE_LIMIT_WAIT_RESULT: Option<i64> = std::env::var("QUEUE_LIMIT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok());
|
||||
pub static ref TIMEOUT_WAIT_RESULT: i32 = std::env::var("TIMEOUT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(20);
|
||||
}
|
||||
pub async fn run_wait_result_job_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
|
||||
Extension(queue_limit): Extension<Arc<QueueLimitWaitResult>>,
|
||||
Path((w_id, script_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
headers: HeaderMap,
|
||||
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
|
||||
) -> error::JsonResult<serde_json::Value> {
|
||||
check_queue_too_long(db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?;
|
||||
check_queue_too_long(db, queue_limit.0.or(run_query.queue_limit)).await?;
|
||||
let script_path = script_path.to_path();
|
||||
let mut tx = user_db.clone().begin(&authed).await?;
|
||||
let job_payload = script_path_to_payload(script_path, &mut tx, &w_id).await?;
|
||||
@@ -1361,7 +1356,7 @@ pub async fn run_wait_result_job_by_path(
|
||||
run_wait_result(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
*TIMEOUT_WAIT_RESULT,
|
||||
timeout.0,
|
||||
uuid,
|
||||
Path((w_id, script_path)),
|
||||
)
|
||||
@@ -1372,6 +1367,7 @@ pub async fn run_wait_result_job_by_hash(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
|
||||
Path((w_id, script_hash)): Path<(String, ScriptHash)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
headers: HeaderMap,
|
||||
@@ -1407,7 +1403,7 @@ pub async fn run_wait_result_job_by_hash(
|
||||
run_wait_result(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
*TIMEOUT_WAIT_RESULT,
|
||||
timeout.0,
|
||||
uuid,
|
||||
Path((w_id, script_hash)),
|
||||
)
|
||||
@@ -1418,6 +1414,7 @@ pub async fn run_wait_result_flow_by_path(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(timeout): Extension<Arc<TimeoutWaitResult>>,
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Query(run_query): Query<RunJobQuery>,
|
||||
headers: HeaderMap,
|
||||
@@ -1453,7 +1450,7 @@ pub async fn run_wait_result_flow_by_path(
|
||||
run_wait_result(
|
||||
authed,
|
||||
Extension(user_db),
|
||||
*TIMEOUT_WAIT_RESULT,
|
||||
timeout.0,
|
||||
uuid,
|
||||
Path((w_id, flow_path)),
|
||||
)
|
||||
|
||||
@@ -6,17 +6,15 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use crate::oauth2::AllClients;
|
||||
use argon2::Argon2;
|
||||
use axum::{middleware::from_extractor, routing::get, Extension, Router};
|
||||
use db::DB;
|
||||
use git_version::git_version;
|
||||
use reqwest::Client;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_cookies::CookieManagerLayer;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use windmill_common::utils::rd_string;
|
||||
use windmill_common::{error::to_anyhow, utils::rd_string};
|
||||
|
||||
use crate::{
|
||||
db::UserDB,
|
||||
@@ -52,32 +50,20 @@ mod workspaces;
|
||||
pub const GIT_VERSION: &str =
|
||||
git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
|
||||
|
||||
pub struct BaseUrl(String);
|
||||
pub struct IsSecure(bool);
|
||||
pub struct CookieDomain(Option<String>);
|
||||
pub struct CloudHosted(bool);
|
||||
pub struct ContentSecurityPolicy(String);
|
||||
pub struct TimeoutWaitResult(i32);
|
||||
pub struct QueueLimitWaitResult(Option<i64>);
|
||||
|
||||
pub use users::delete_expired_items_perdiodically;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref BASE_URL: String = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
|
||||
|
||||
pub static ref COOKIE_DOMAIN: Option<String> = std::env::var("COOKIE_DOMAIN").ok();
|
||||
|
||||
pub static ref SLACK_SIGNING_SECRET: Option<SlackVerifier> = std::env::var("SLACK_SIGNING_SECRET")
|
||||
.ok()
|
||||
.map(|x| SlackVerifier::new(x).unwrap());
|
||||
|
||||
static ref IS_SECURE: bool = BASE_URL.starts_with("https://");
|
||||
|
||||
pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build().unwrap();
|
||||
|
||||
pub static ref OAUTH_CLIENTS: AllClients = build_oauth_clients(&BASE_URL)
|
||||
.map_err(|e| tracing::error!("Error building oauth clients: {}", e))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn run_server(
|
||||
db: DB,
|
||||
addr: SocketAddr,
|
||||
base_url: String,
|
||||
mut rx: tokio::sync::broadcast::Receiver<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
let user_db = UserDB::new(db.clone());
|
||||
@@ -87,7 +73,16 @@ pub async fn run_server(
|
||||
std::env::var("SUPERADMIN_SECRET").ok(),
|
||||
));
|
||||
let argon2 = Arc::new(Argon2::default());
|
||||
|
||||
let basic_clients = Arc::new(build_oauth_clients(&base_url).await?);
|
||||
let slack_verifier = Arc::new(
|
||||
std::env::var("SLACK_SIGNING_SECRET")
|
||||
.ok()
|
||||
.map(|x| SlackVerifier::new(x).unwrap()),
|
||||
);
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?;
|
||||
let middleware_stack = ServiceBuilder::new()
|
||||
.layer(
|
||||
TraceLayer::new_for_http()
|
||||
@@ -98,6 +93,21 @@ pub async fn run_server(
|
||||
.layer(Extension(db.clone()))
|
||||
.layer(Extension(user_db))
|
||||
.layer(Extension(auth_cache.clone()))
|
||||
.layer(Extension(basic_clients))
|
||||
.layer(Extension(Arc::new(BaseUrl(base_url.to_string()))))
|
||||
.layer(Extension(Arc::new(ContentSecurityPolicy(
|
||||
std::env::var("SERVE_CSP").unwrap_or("".to_owned()),
|
||||
))))
|
||||
.layer(Extension(Arc::new(CloudHosted(
|
||||
std::env::var("CLOUD_HOSTED").is_ok(),
|
||||
))))
|
||||
.layer(Extension(Arc::new(IsSecure(
|
||||
base_url.starts_with("https://"),
|
||||
))))
|
||||
.layer(Extension(Arc::new(CookieDomain(
|
||||
std::env::var("COOKIE_DOMAIN").ok(),
|
||||
))))
|
||||
.layer(Extension(http_client))
|
||||
.layer(CookieManagerLayer::new())
|
||||
.layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone())));
|
||||
// build our application with a route
|
||||
@@ -109,7 +119,21 @@ pub async fn run_server(
|
||||
"/w/:workspace_id",
|
||||
Router::new()
|
||||
.nest("/scripts", scripts::workspaced_service())
|
||||
.nest("/jobs", jobs::workspaced_service())
|
||||
.nest(
|
||||
"/jobs",
|
||||
jobs::workspaced_service()
|
||||
.layer(Extension(Arc::new(TimeoutWaitResult(
|
||||
std::env::var("TIMEOUT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(20),
|
||||
))))
|
||||
.layer(Extension(Arc::new(QueueLimitWaitResult(
|
||||
std::env::var("QUEUE_LIMIT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok()),
|
||||
)))),
|
||||
)
|
||||
.nest(
|
||||
"/users",
|
||||
users::workspaced_service().layer(Extension(argon2.clone())),
|
||||
@@ -150,7 +174,10 @@ pub async fn run_server(
|
||||
"/auth",
|
||||
users::make_unauthed_service().layer(Extension(argon2)),
|
||||
)
|
||||
.nest("/oauth", oauth2::global_service())
|
||||
.nest(
|
||||
"/oauth",
|
||||
oauth2::global_service().layer(Extension(slack_verifier)),
|
||||
)
|
||||
.route("/version", get(git_v))
|
||||
.route("/openapi.yaml", get(openapi)),
|
||||
)
|
||||
|
||||
70
backend/windmill-api/src/main.rs
Normal file
70
backend/windmill-api/src/main.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2022
|
||||
* This file and its contents are licensed under the AGPLv3 License.
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::Ok;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
windmill_common::tracing_init::initialize_tracing();
|
||||
|
||||
let db = windmill_common::connect_db(true).await?;
|
||||
|
||||
let num_workers = std::env::var("NUM_WORKERS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
.unwrap_or(windmill_common::DEFAULT_NUM_WORKERS as i32);
|
||||
|
||||
let metrics_addr: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
.ok()
|
||||
.map(|s| {
|
||||
s.parse::<bool>()
|
||||
.map(|b| b.then(|| SocketAddr::from(([0, 0, 0, 0], 8001))))
|
||||
.or_else(|_| s.parse::<SocketAddr>().map(Some))
|
||||
})
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
let server_mode = !std::env::var("DISABLE_SERVER")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if server_mode {
|
||||
windmill_api::migrate_db(&db).await?;
|
||||
}
|
||||
|
||||
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
let shutdown_signal = windmill_common::shutdown_signal(tx);
|
||||
|
||||
let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
|
||||
if server_mode || num_workers > 0 {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 8000));
|
||||
|
||||
let server_f = async {
|
||||
if server_mode {
|
||||
windmill_api::run_server(db.clone(), addr, base_url, rx.resubscribe()).await?;
|
||||
}
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
|
||||
let metrics_f = async {
|
||||
match metrics_addr {
|
||||
Some(addr) => windmill_common::serve_metrics(addr, rx.resubscribe())
|
||||
.await
|
||||
.map_err(anyhow::Error::from),
|
||||
None => Ok(()),
|
||||
}
|
||||
};
|
||||
|
||||
futures::try_join!(shutdown_signal, server_f, metrics_f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// /*
|
||||
// * Author: Ruben Fiszel
|
||||
// * Copyright: Windmill Labs, Inc 2022
|
||||
// * This file and its contents are licensed under the AGPLv3 License.
|
||||
// * Please see the included NOTICE for copyright information and
|
||||
// * LICENSE-AGPL for a copy of the license.
|
||||
// */
|
||||
// use std::net::SocketAddr;
|
||||
|
||||
// use anyhow::Ok;
|
||||
|
||||
// pub const DEFAULT_NUM_WORKERS: usize = 3;
|
||||
|
||||
// #[tokio::main]
|
||||
// async fn main() -> anyhow::Result<()> {
|
||||
// windmill_common::tracing_init::initialize_tracing();
|
||||
|
||||
// let db = windmill_common::connect_db(true).await?;
|
||||
|
||||
// let num_workers = std::env::var("NUM_WORKERS")
|
||||
// .ok()
|
||||
// .and_then(|x| x.parse::<i32>().ok())
|
||||
// .unwrap_or(DEFAULT_NUM_WORKERS as i32);
|
||||
|
||||
// let metrics_addr: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
// .ok()
|
||||
// .map(|s| {
|
||||
// s.parse::<bool>()
|
||||
// .map(|b| b.then(|| SocketAddr::from(([0, 0, 0, 0], 8001))))
|
||||
// .or_else(|_| s.parse::<SocketAddr>().map(Some))
|
||||
// })
|
||||
// .transpose()?
|
||||
// .flatten();
|
||||
|
||||
// let server_mode = !std::env::var("DISABLE_SERVER")
|
||||
// .ok()
|
||||
// .and_then(|x| x.parse::<bool>().ok())
|
||||
// .unwrap_or(false);
|
||||
|
||||
// if server_mode {
|
||||
// windmill_api::migrate_db(&db).await?;
|
||||
// }
|
||||
|
||||
// let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
// let shutdown_signal = windmill_common::shutdown_signal(tx);
|
||||
|
||||
// if server_mode || num_workers > 0 {
|
||||
// let addr = SocketAddr::from(([0, 0, 0, 0], 8000));
|
||||
|
||||
// let server_f = async {
|
||||
// if server_mode {
|
||||
// windmill_api::run_server(db.clone(), addr, rx.resubscribe()).await?;
|
||||
// }
|
||||
// Ok(()) as anyhow::Result<()>
|
||||
// };
|
||||
|
||||
// let metrics_f = async {
|
||||
// match metrics_addr {
|
||||
// Some(addr) => windmill_common::serve_metrics(addr, rx.resubscribe())
|
||||
// .await
|
||||
// .map_err(anyhow::Error::from),
|
||||
// None => Ok(()),
|
||||
// }
|
||||
// };
|
||||
|
||||
// futures::try_join!(shutdown_signal, server_f, metrics_f)?;
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::extract::FromRequestParts;
|
||||
use axum::http::request::Parts;
|
||||
@@ -27,6 +29,7 @@ use oauth2::{Client as OClient, *};
|
||||
use reqwest::Client;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
@@ -38,14 +41,15 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
variables::{build_crypt, encrypt},
|
||||
workspaces::WorkspaceSettings,
|
||||
BaseUrl,
|
||||
};
|
||||
use crate::{BASE_URL, HTTP_CLIENT, IS_SECURE, OAUTH_CLIENTS, SLACK_SIGNING_SECRET};
|
||||
use crate::{CookieDomain, IsSecure};
|
||||
use windmill_common::error::{self, to_anyhow, Error};
|
||||
use windmill_common::oauth2::*;
|
||||
|
||||
use windmill_queue::JobPayload;
|
||||
|
||||
use std::{fs, str};
|
||||
use std::str;
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
@@ -92,6 +96,7 @@ pub struct OAuthConfig {
|
||||
extra_params: Option<HashMap<String, String>>,
|
||||
extra_params_callback: Option<HashMap<String, String>>,
|
||||
req_body_auth: Option<bool>,
|
||||
pkce: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -108,7 +113,7 @@ pub struct AllClients {
|
||||
pub slack: Option<OClient>,
|
||||
}
|
||||
|
||||
pub fn build_oauth_clients(base_url: &str) -> anyhow::Result<AllClients> {
|
||||
pub async fn build_oauth_clients(base_url: &str) -> anyhow::Result<AllClients> {
|
||||
let connect_configs = serde_json::from_str::<HashMap<String, OAuthConfig>>(include_str!(
|
||||
"../../oauth_connect.json"
|
||||
))?;
|
||||
@@ -116,12 +121,14 @@ pub fn build_oauth_clients(base_url: &str) -> anyhow::Result<AllClients> {
|
||||
"../../oauth_login.json"
|
||||
))?;
|
||||
|
||||
let mut content = String::new();
|
||||
let path = "./oauth.json";
|
||||
let content = if std::path::Path::new(path).exists() {
|
||||
fs::read_to_string(path).map_err(to_anyhow)?
|
||||
if std::path::Path::new(path).exists() {
|
||||
let mut file = File::open(path).await?;
|
||||
file.read_to_string(&mut content).await?;
|
||||
} else {
|
||||
"{}".to_string()
|
||||
};
|
||||
content.push_str("{}");
|
||||
}
|
||||
|
||||
let oauths: HashMap<String, OAuthClient> =
|
||||
match serde_json::from_str::<HashMap<String, OAuthClient>>(&content) {
|
||||
@@ -209,6 +216,7 @@ pub fn build_oauth_clients(base_url: &str) -> anyhow::Result<AllClients> {
|
||||
extra_params: None,
|
||||
extra_params_callback: None,
|
||||
req_body_auth: None,
|
||||
pkce: None,
|
||||
},
|
||||
v.clone(),
|
||||
false,
|
||||
@@ -283,10 +291,12 @@ pub struct SlackBotToken {
|
||||
async fn connect(
|
||||
Path(client_name): Path<String>,
|
||||
Query(query): Query<HashMap<String, String>>,
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(is_secure): Extension<Arc<IsSecure>>,
|
||||
cookies: Cookies,
|
||||
) -> error::Result<Redirect> {
|
||||
let mut query = query.clone();
|
||||
let connects = &OAUTH_CLIENTS.connects;
|
||||
let connects = &clients.connects;
|
||||
let scopes = query
|
||||
.get("scopes")
|
||||
.map(|x| x.split('+').map(|x| x.to_owned()).collect());
|
||||
@@ -302,7 +312,7 @@ async fn connect(
|
||||
cookies,
|
||||
scopes,
|
||||
extra_params,
|
||||
*IS_SECURE,
|
||||
is_secure.0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -369,9 +379,11 @@ async fn delete_account(
|
||||
Ok(format!("Deleted account id {id}"))
|
||||
}
|
||||
|
||||
async fn list_logins() -> error::JsonResult<Vec<String>> {
|
||||
async fn list_logins(
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
) -> error::JsonResult<Vec<String>> {
|
||||
Ok(Json(
|
||||
OAUTH_CLIENTS
|
||||
clients
|
||||
.logins
|
||||
.keys()
|
||||
.map(|x| x.to_owned())
|
||||
@@ -384,9 +396,11 @@ struct ScopesAndParams {
|
||||
scopes: Vec<String>,
|
||||
extra_params: Option<HashMap<String, String>>,
|
||||
}
|
||||
async fn list_connects() -> error::JsonResult<HashMap<String, ScopesAndParams>> {
|
||||
async fn list_connects(
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
) -> error::JsonResult<HashMap<String, ScopesAndParams>> {
|
||||
Ok(Json(
|
||||
(&OAUTH_CLIENTS.connects)
|
||||
(&clients.connects)
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
@@ -401,8 +415,12 @@ async fn list_connects() -> error::JsonResult<HashMap<String, ScopesAndParams>>
|
||||
))
|
||||
}
|
||||
|
||||
async fn connect_slack(cookies: Cookies) -> error::Result<Redirect> {
|
||||
let mut client = OAUTH_CLIENTS
|
||||
async fn connect_slack(
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(is_secure): Extension<Arc<IsSecure>>,
|
||||
cookies: Cookies,
|
||||
) -> error::Result<Redirect> {
|
||||
let mut client = clients
|
||||
.slack
|
||||
.as_ref()
|
||||
.ok_or_else(|| error::Error::BadRequest("slack client not setup".to_string()))?
|
||||
@@ -413,7 +431,7 @@ async fn connect_slack(cookies: Cookies) -> error::Result<Redirect> {
|
||||
client.add_scope("commands");
|
||||
let url = client.authorize_url(&state);
|
||||
|
||||
set_cookie(&state, cookies, *IS_SECURE);
|
||||
set_cookie(&state, cookies, is_secure.0);
|
||||
Ok(Redirect::to(url.as_str()))
|
||||
}
|
||||
|
||||
@@ -455,9 +473,14 @@ async fn disconnect_slack(
|
||||
Ok(format!("slack disconnected"))
|
||||
}
|
||||
|
||||
async fn login(Path(client_name): Path<String>, cookies: Cookies) -> error::Result<Redirect> {
|
||||
let clients = &OAUTH_CLIENTS.logins;
|
||||
oauth_redirect(clients, client_name, cookies, None, None, *IS_SECURE)
|
||||
async fn login(
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(is_secure): Extension<Arc<IsSecure>>,
|
||||
Path(client_name): Path<String>,
|
||||
cookies: Cookies,
|
||||
) -> error::Result<Redirect> {
|
||||
let clients = &clients.logins;
|
||||
oauth_redirect(clients, client_name, cookies, None, None, is_secure.0)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -468,11 +491,13 @@ async fn refresh_token(
|
||||
authed: Authed,
|
||||
Path((w_id, id)): Path<(String, i32)>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Json(VariablePath { path }): Json<VariablePath>,
|
||||
) -> error::Result<String> {
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
|
||||
_refresh_token(tx, &path, w_id, id).await?;
|
||||
_refresh_token(tx, &path, w_id, id, clients, http_client).await?;
|
||||
|
||||
Ok(format!("Token at path {path} refreshed"))
|
||||
}
|
||||
@@ -482,6 +507,8 @@ pub async fn _refresh_token<'c>(
|
||||
path: &str,
|
||||
w_id: String,
|
||||
id: i32,
|
||||
clients: Arc<AllClients>,
|
||||
http_client: Client,
|
||||
) -> error::Result<String> {
|
||||
let account = sqlx::query!(
|
||||
"SELECT client, refresh_token FROM account WHERE workspace_id = $1 AND id = $2",
|
||||
@@ -491,14 +518,14 @@ pub async fn _refresh_token<'c>(
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
let account = not_found_if_none(account, "Account", &id.to_string())?;
|
||||
let client = (&OAUTH_CLIENTS
|
||||
let client = (&clients
|
||||
.connects
|
||||
.get(&account.client)
|
||||
.ok_or_else(|| error::Error::BadRequest("invalid client".to_string()))?
|
||||
.client)
|
||||
.to_owned();
|
||||
|
||||
let token = _exchange_token(client, &account.refresh_token).await;
|
||||
let token = _exchange_token(client, &account.refresh_token, http_client).await;
|
||||
|
||||
if let Err(token_err) = token {
|
||||
sqlx::query!(
|
||||
@@ -556,10 +583,14 @@ pub async fn _refresh_token<'c>(
|
||||
Ok(token_str)
|
||||
}
|
||||
|
||||
async fn _exchange_token(client: OClient, refresh_token: &str) -> Result<TokenResponse, Error> {
|
||||
async fn _exchange_token(
|
||||
client: OClient,
|
||||
refresh_token: &str,
|
||||
http_client: Client,
|
||||
) -> Result<TokenResponse, Error> {
|
||||
let token_json = client
|
||||
.exchange_refresh_token(&RefreshToken::from(refresh_token.clone()))
|
||||
.with_client(&HTTP_CLIENT)
|
||||
.with_client(&http_client)
|
||||
.execute::<serde_json::Value>()
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
@@ -580,9 +611,11 @@ pub struct OAuthCallback {
|
||||
async fn connect_callback(
|
||||
cookies: Cookies,
|
||||
Path(client_name): Path<String>,
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Json(callback): Json<OAuthCallback>,
|
||||
) -> error::JsonResult<TokenResponse> {
|
||||
let client_w_scopes = OAUTH_CLIENTS
|
||||
let client_w_scopes = &clients
|
||||
.connects
|
||||
.get(&client_name)
|
||||
.ok_or_else(|| error::Error::BadRequest("invalid client".to_string()))?;
|
||||
@@ -590,7 +623,7 @@ async fn connect_callback(
|
||||
let client = client_w_scopes.client.to_owned();
|
||||
let extra_params = client_w_scopes.extra_params_callback.clone();
|
||||
let token_response =
|
||||
exchange_code::<TokenResponse>(callback, &cookies, client, &HTTP_CLIENT, extra_params)
|
||||
exchange_code::<TokenResponse>(callback, &cookies, client, &http_client, extra_params)
|
||||
.await?;
|
||||
|
||||
Ok(Json(token_response))
|
||||
@@ -601,15 +634,17 @@ async fn connect_slack_callback(
|
||||
authed: Authed,
|
||||
cookies: Cookies,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Json(callback): Json<OAuthCallback>,
|
||||
) -> error::Result<String> {
|
||||
let client = OAUTH_CLIENTS
|
||||
let client = clients
|
||||
.slack
|
||||
.as_ref()
|
||||
.ok_or_else(|| error::Error::BadRequest("slack client not setup".to_string()))?
|
||||
.to_owned();
|
||||
let token =
|
||||
exchange_code::<SlackTokenResponse>(callback, &cookies, client, &HTTP_CLIENT, None).await?;
|
||||
exchange_code::<SlackTokenResponse>(callback, &cookies, client, &http_client, None).await?;
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
@@ -727,14 +762,16 @@ where
|
||||
|
||||
async fn slack_command(
|
||||
SlackSig { sig, ts }: SlackSig,
|
||||
Extension(slack_verifier): Extension<Arc<Option<SlackVerifier>>>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
body: Bytes,
|
||||
) -> error::Result<String> {
|
||||
let form: SlackCommand = serde_urlencoded::from_bytes(&body)
|
||||
.map_err(|_| error::Error::BadRequest("invalid payload".to_string()))?;
|
||||
|
||||
let body = String::from_utf8_lossy(&body);
|
||||
if SLACK_SIGNING_SECRET
|
||||
if slack_verifier
|
||||
.as_ref()
|
||||
.as_ref()
|
||||
.map(|sv| sv.verify(&ts, &body, &sig).ok())
|
||||
@@ -792,7 +829,7 @@ async fn slack_command(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
let url = BASE_URL.to_owned();
|
||||
let url = base_url.0.to_owned();
|
||||
return Ok(format!(
|
||||
"Job launched. See details at {url}/run/{uuid}?workspace={}",
|
||||
&settings.workspace_id
|
||||
@@ -817,27 +854,31 @@ pub struct UserInfo {
|
||||
async fn login_callback(
|
||||
Path(client_name): Path<String>,
|
||||
cookies: Cookies,
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Extension(is_secure): Extension<Arc<IsSecure>>,
|
||||
Extension(cookie_domain): Extension<Arc<CookieDomain>>,
|
||||
Json(callback): Json<OAuthCallback>,
|
||||
) -> error::Result<String> {
|
||||
let client_w_config = &OAUTH_CLIENTS
|
||||
let client_w_config = &clients
|
||||
.logins
|
||||
.get(&client_name)
|
||||
.ok_or_else(|| error::Error::BadRequest("invalid client".to_string()))?;
|
||||
let client = client_w_config.client.to_owned();
|
||||
let token_res =
|
||||
exchange_code::<TokenResponse>(callback, &cookies, client, &HTTP_CLIENT, None).await;
|
||||
exchange_code::<TokenResponse>(callback, &cookies, client, &http_client, None).await;
|
||||
|
||||
if let Ok(token) = token_res {
|
||||
let token = &token.access_token.to_string();
|
||||
let userinfo_url = client_w_config.userinfo_url.as_ref().ok_or_else(|| {
|
||||
Error::BadConfig(format!("Missing userinfo_url in client {client_name}"))
|
||||
})?;
|
||||
let user = http_get_user_info::<UserInfo>(&HTTP_CLIENT, userinfo_url, token).await?;
|
||||
let user = http_get_user_info::<UserInfo>(&http_client, userinfo_url, token).await?;
|
||||
|
||||
let email = match client_name.as_str() {
|
||||
"github" => http_get_user_info::<Vec<GHEmailInfo>>(
|
||||
&HTTP_CLIENT,
|
||||
&http_client,
|
||||
"https://api.github.com/user/emails",
|
||||
token,
|
||||
)
|
||||
@@ -873,7 +914,15 @@ async fn login_callback(
|
||||
if let Some((email, login_type, super_admin)) = login {
|
||||
let login_type = serde_json::json!(login_type);
|
||||
if login_type == client_name {
|
||||
crate::users::create_session_token(&email, super_admin, &mut tx, cookies).await?;
|
||||
crate::users::create_session_token(
|
||||
&email,
|
||||
super_admin,
|
||||
&mut tx,
|
||||
cookies,
|
||||
is_secure.0,
|
||||
&cookie_domain.as_ref().0,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"an user with the email associated to this login exists but with a different \
|
||||
@@ -908,7 +957,15 @@ async fn login_callback(
|
||||
tx.commit().await?;
|
||||
invite_user_to_all_auto_invite_worspaces(&db, &email).await?;
|
||||
tx = db.begin().await?;
|
||||
crate::users::create_session_token(&email, false, &mut tx, cookies).await?;
|
||||
crate::users::create_session_token(
|
||||
&email,
|
||||
false,
|
||||
&mut tx,
|
||||
cookies,
|
||||
is_secure.0,
|
||||
&cookie_domain.as_ref().0,
|
||||
)
|
||||
.await?;
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&email,
|
||||
@@ -943,7 +1000,7 @@ async fn login_callback(
|
||||
tx.commit().await?;
|
||||
|
||||
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
|
||||
let _ = HTTP_CLIENT
|
||||
let _ = http_client
|
||||
.post(&new_user_webhook)
|
||||
.json(&serde_json::json!({"email" : &email, "event": "oauth_signup"}))
|
||||
.send()
|
||||
@@ -1056,14 +1113,19 @@ fn oauth_redirect(
|
||||
}
|
||||
|
||||
let mut auth_url = client.authorize_url(&state);
|
||||
|
||||
if let Some(extra_params) = extra_params {
|
||||
{
|
||||
let mut query_string = auth_url.query_pairs_mut();
|
||||
for (key, value) in extra_params {
|
||||
|
||||
let pkce = PkceCodeVerifierS256::new_random();
|
||||
for (key, value) in pkce.authorize_url_params() {
|
||||
query_string.append_pair(&key, &value);
|
||||
}
|
||||
if let Some(extra_params) = extra_params {
|
||||
for (key, value) in extra_params {
|
||||
query_string.append_pair(&key, &value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_cookie(&state, cookies, is_secure);
|
||||
Ok(Redirect::to(auth_url.as_str()))
|
||||
}
|
||||
@@ -1072,7 +1134,7 @@ fn set_cookie(state: &State, cookies: Cookies, is_secure: bool) {
|
||||
let csrf = state.to_base64();
|
||||
let mut cookie = Cookie::new("csrf", csrf);
|
||||
cookie.set_secure(is_secure);
|
||||
cookie.set_same_site(Some(cookie::SameSite::Lax));
|
||||
cookie.set_same_site(cookie::SameSite::Lax);
|
||||
cookie.set_http_only(true);
|
||||
cookie.set_path("/");
|
||||
cookies.add(cookie);
|
||||
|
||||
@@ -142,7 +142,7 @@ async fn list_resources(
|
||||
.join("account")
|
||||
.on("variable.account = account.id AND account.workspace_id = variable.workspace_id")
|
||||
.order_by("path", true)
|
||||
.and_where("resource.workspace_id = ?".bind(&w_id))
|
||||
.and_where("resource.workspace_id = ? OR resource.workspace_id = 'starter'".bind(&w_id))
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.clone();
|
||||
@@ -187,7 +187,7 @@ async fn get_resource(
|
||||
FROM resource
|
||||
LEFT JOIN variable ON variable.path = resource.path AND variable.workspace_id = resource.workspace_id
|
||||
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = resource.workspace_id
|
||||
WHERE resource.path = $1 AND resource.workspace_id = $2",
|
||||
WHERE resource.path = $1 AND (resource.workspace_id = $2 OR resource.workspace_id = 'starter')",
|
||||
path.to_owned(),
|
||||
&w_id
|
||||
)
|
||||
@@ -226,7 +226,8 @@ async fn get_resource_value(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let value_o = sqlx::query_scalar!(
|
||||
"SELECT value from resource WHERE path = $1 AND workspace_id = $2",
|
||||
"SELECT value from resource WHERE path = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter')",
|
||||
path.to_owned(),
|
||||
&w_id
|
||||
)
|
||||
@@ -477,7 +478,7 @@ async fn list_resource_types(
|
||||
) -> JsonResult<Vec<ResourceType>> {
|
||||
let rows = sqlx::query_as!(
|
||||
ResourceType,
|
||||
"SELECT * from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') ORDER \
|
||||
"SELECT * from resource_type WHERE (workspace_id = $1 OR workspace_id = 'starter' OR workspace_id = 'admins') ORDER \
|
||||
BY name",
|
||||
&w_id
|
||||
)
|
||||
@@ -492,7 +493,7 @@ async fn list_resource_types_names(
|
||||
Path(w_id): Path<String>,
|
||||
) -> JsonResult<Vec<String>> {
|
||||
let rows = sqlx::query_scalar!(
|
||||
"SELECT name from resource_type WHERE (workspace_id = $1 OR workspace_id = 'admins') \
|
||||
"SELECT name from resource_type WHERE (workspace_id = $1 OR workspace_id = 'starter' OR workspace_id = 'admins') \
|
||||
ORDER BY name",
|
||||
&w_id
|
||||
)
|
||||
@@ -511,7 +512,8 @@ async fn get_resource_type(
|
||||
|
||||
let resource_type_o = sqlx::query_as!(
|
||||
ResourceType,
|
||||
"SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins')",
|
||||
"SELECT * from resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter' OR workspace_id = 'admins')",
|
||||
&name,
|
||||
&w_id
|
||||
)
|
||||
@@ -528,7 +530,8 @@ async fn exists_resource_type(
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
) -> JsonResult<bool> {
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = 'admins'))",
|
||||
"SELECT EXISTS(SELECT 1 FROM resource_type WHERE name = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter' OR workspace_id = 'admins'))",
|
||||
name,
|
||||
w_id
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use reqwest::Client;
|
||||
use sql_builder::prelude::*;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
|
||||
@@ -14,7 +15,6 @@ use crate::{
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
@@ -111,7 +111,7 @@ async fn list_scripts(
|
||||
)
|
||||
.order_desc("favorite.path IS NOT NULL")
|
||||
.order_by("created_at", lq.order_desc.unwrap_or(true))
|
||||
.and_where("o.workspace_id = ?".bind(&w_id))
|
||||
.and_where("o.workspace_id = ? OR o.workspace_id = 'starter'".bind(&w_id))
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
.clone();
|
||||
@@ -120,7 +120,7 @@ async fn list_scripts(
|
||||
sqlb.and_where_eq(
|
||||
"created_at",
|
||||
"(select max(created_at) from script where o.path = path
|
||||
AND workspace_id = ?)"
|
||||
AND (workspace_id = ? OR workspace_id = 'starter'))"
|
||||
.bind(&w_id),
|
||||
);
|
||||
} else {
|
||||
@@ -163,9 +163,12 @@ async fn list_scripts(
|
||||
Ok(Json(rows))
|
||||
}
|
||||
|
||||
async fn list_hub_scripts(Authed { email, .. }: Authed) -> JsonResult<serde_json::Value> {
|
||||
async fn list_hub_scripts(
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let asks = list_elems_from_hub(
|
||||
&HTTP_CLIENT,
|
||||
http_client,
|
||||
"https://hub.windmill.dev/searchData?approved=true",
|
||||
&email,
|
||||
)
|
||||
@@ -439,16 +442,21 @@ async fn create_script(
|
||||
Ok((StatusCode::CREATED, format!("{}", hash)))
|
||||
}
|
||||
|
||||
pub async fn get_hub_script_by_path(authed: Authed, Path(path): Path<StripPath>) -> Result<String> {
|
||||
windmill_common::scripts::get_hub_script_by_path(&authed.email, path, &HTTP_CLIENT).await
|
||||
pub async fn get_hub_script_by_path(
|
||||
authed: Authed,
|
||||
Path(path): Path<StripPath>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> Result<String> {
|
||||
windmill_common::scripts::get_hub_script_by_path(&authed.email, path, http_client).await
|
||||
}
|
||||
|
||||
pub async fn get_full_hub_script_by_path(
|
||||
Authed { email, .. }: Authed,
|
||||
Path(path): Path<StripPath>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<HubScript> {
|
||||
Ok(Json(
|
||||
windmill_common::scripts::get_full_hub_script_by_path(&email, path, &HTTP_CLIENT).await?,
|
||||
windmill_common::scripts::get_full_hub_script_by_path(&email, path, http_client).await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -461,9 +469,9 @@ async fn get_script_by_path(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let script_o = sqlx::query_as::<_, Script>(
|
||||
"SELECT * FROM script WHERE path = $1 AND workspace_id = $2 \
|
||||
"SELECT * FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') \
|
||||
AND created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND \
|
||||
workspace_id = $2)",
|
||||
(workspace_id = $2 OR workspace_id = 'starter'))",
|
||||
)
|
||||
.bind(path)
|
||||
.bind(w_id)
|
||||
@@ -502,12 +510,11 @@ async fn raw_script_by_path(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
let content_o = sqlx::query_scalar!(
|
||||
"SELECT content FROM script WHERE path = $1 AND workspace_id = $2 \
|
||||
"SELECT content FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') \
|
||||
AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND \
|
||||
workspace_id = $2)",
|
||||
path,
|
||||
w_id
|
||||
(workspace_id = $2 OR workspace_id = 'starter'))",
|
||||
path, w_id
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
@@ -524,8 +531,10 @@ async fn exists_script_by_path(
|
||||
let path = path.to_path();
|
||||
|
||||
let exists = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2))",
|
||||
"SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND (workspace_id = $2 OR \
|
||||
workspace_id = 'starter') AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND (workspace_id = $2 \
|
||||
OR workspace_id = 'starter')))",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
@@ -541,12 +550,13 @@ async fn get_script_by_hash_internal<'c>(
|
||||
workspace_id: &str,
|
||||
hash: &ScriptHash,
|
||||
) -> Result<Script> {
|
||||
let script_o =
|
||||
sqlx::query_as::<_, Script>("SELECT * FROM script WHERE hash = $1 AND workspace_id = $2")
|
||||
.bind(hash)
|
||||
.bind(workspace_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
let script_o = sqlx::query_as::<_, Script>(
|
||||
"SELECT * FROM script WHERE hash = $1 AND (workspace_id = $2 OR workspace_id = 'starter')",
|
||||
)
|
||||
.bind(hash)
|
||||
.bind(workspace_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
let script = not_found_if_none(script_o, "Script", hash.to_string())?;
|
||||
Ok(script)
|
||||
@@ -592,7 +602,8 @@ async fn get_deployment_status(
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let status_o: Option<DeploymentStatus> = sqlx::query_as!(
|
||||
DeploymentStatus,
|
||||
"SELECT lock, lock_error_logs FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
"SELECT lock, lock_error_logs FROM script WHERE hash = $1 AND (workspace_id = $2 OR \
|
||||
workspace_id = 'starter')",
|
||||
hash.0,
|
||||
w_id,
|
||||
)
|
||||
|
||||
@@ -9,41 +9,58 @@
|
||||
use axum::{
|
||||
body::{self, BoxBody},
|
||||
extract::OriginalUri,
|
||||
http::{header, Response},
|
||||
http::{header, response::Builder, Response},
|
||||
response::IntoResponse,
|
||||
Extension,
|
||||
};
|
||||
|
||||
use hyper::Uri;
|
||||
use crate::{CloudHosted, ContentSecurityPolicy, IsSecure};
|
||||
use mime_guess::mime;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::sync::Arc;
|
||||
|
||||
// static_handler is a handler that serves static files from the
|
||||
pub async fn static_handler(OriginalUri(original_uri): OriginalUri) -> StaticFile {
|
||||
StaticFile(original_uri)
|
||||
pub async fn static_handler(
|
||||
Extension(is_secure): Extension<Arc<IsSecure>>,
|
||||
Extension(is_cloud_hosted): Extension<Arc<CloudHosted>>,
|
||||
Extension(csp): Extension<Arc<ContentSecurityPolicy>>,
|
||||
OriginalUri(original_uri): OriginalUri,
|
||||
) -> StaticFile {
|
||||
let path = original_uri.path().trim_start_matches('/').to_string();
|
||||
StaticFile(path, is_secure.0, is_cloud_hosted.0, csp)
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../frontend/build/"]
|
||||
struct Asset;
|
||||
pub struct StaticFile(Uri);
|
||||
pub struct StaticFile(
|
||||
pub String,
|
||||
pub bool,
|
||||
pub bool,
|
||||
pub Arc<ContentSecurityPolicy>,
|
||||
);
|
||||
|
||||
impl IntoResponse for StaticFile {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let path = self.0.path().trim_start_matches('/');
|
||||
serve_path(path)
|
||||
let path = self.0;
|
||||
let can_set_security_headers = self.1 && self.2;
|
||||
let csp = self.3;
|
||||
serve_path(path, can_set_security_headers, csp)
|
||||
}
|
||||
}
|
||||
|
||||
const TWO_HUNDRED: &str = "200.html";
|
||||
|
||||
fn serve_path(path: &str) -> Response<BoxBody> {
|
||||
fn serve_path(
|
||||
path: String,
|
||||
can_set_security_headers: bool,
|
||||
csp: Arc<ContentSecurityPolicy>,
|
||||
) -> Response<BoxBody> {
|
||||
if path.starts_with("api/") {
|
||||
return Response::builder()
|
||||
.status(404)
|
||||
.body(body::boxed(body::Empty::new()))
|
||||
.unwrap();
|
||||
}
|
||||
match Asset::get(path) {
|
||||
match Asset::get(path.as_str()) {
|
||||
Some(content) => {
|
||||
let body = body::boxed(body::Full::from(content.data));
|
||||
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
||||
@@ -58,12 +75,26 @@ fn serve_path(path: &str) -> Response<BoxBody> {
|
||||
res = res.header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
}
|
||||
|
||||
if can_set_security_headers {
|
||||
res = set_security_headers(res, csp);
|
||||
}
|
||||
res.body(body).unwrap()
|
||||
}
|
||||
None if path.starts_with("_app/") => Response::builder()
|
||||
None if path.as_str().starts_with("_app/") => Response::builder()
|
||||
.status(404)
|
||||
.body(body::boxed(body::Empty::new()))
|
||||
.unwrap(),
|
||||
None => serve_path(TWO_HUNDRED),
|
||||
None => serve_path("200.html".to_owned(), can_set_security_headers, csp),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_security_headers(mut res: Builder, csp: Arc<ContentSecurityPolicy>) -> Builder {
|
||||
res = res.header("X-Frame-Options", "DENY");
|
||||
res = res.header("X-Content-Type-Options", "nosniff");
|
||||
|
||||
if !csp.0.is_empty() {
|
||||
res = res.header("Content-Security-Policy", &csp.0);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
folders::get_folders_for_user,
|
||||
utils::require_super_admin,
|
||||
workspaces::invite_user_to_all_auto_invite_worspaces,
|
||||
COOKIE_DOMAIN, HTTP_CLIENT, IS_SECURE,
|
||||
CookieDomain, IsSecure,
|
||||
};
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use axum::{
|
||||
@@ -26,6 +26,7 @@ use axum::{
|
||||
};
|
||||
use hyper::{header::LOCATION, StatusCode};
|
||||
use rand::rngs::OsRng;
|
||||
use reqwest::Client;
|
||||
use retainer::Cache;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
@@ -720,12 +721,14 @@ async fn logout(
|
||||
Tokened { token }: Tokened,
|
||||
cookies: Cookies,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(cookie_domain): Extension<Arc<CookieDomain>>,
|
||||
Query(LogoutQuery { rd }): Query<LogoutQuery>,
|
||||
) -> Result<Response> {
|
||||
let mut cookie = Cookie::new(COOKIE_NAME, "");
|
||||
cookie.set_path(COOKIE_PATH);
|
||||
if COOKIE_DOMAIN.is_some() {
|
||||
cookie.set_domain(COOKIE_DOMAIN.clone().unwrap());
|
||||
let domain = cookie_domain.0.clone();
|
||||
if domain.is_some() {
|
||||
cookie.set_domain(domain.clone().unwrap());
|
||||
}
|
||||
cookies.remove(cookie);
|
||||
let mut tx = db.begin().await?;
|
||||
@@ -1276,6 +1279,7 @@ async fn create_user(
|
||||
Authed { email, .. }: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(argon2): Extension<Arc<Argon2<'_>>>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Json(nu): Json<NewUser>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut tx = db.begin().await?;
|
||||
@@ -1309,7 +1313,7 @@ async fn create_user(
|
||||
tx.commit().await?;
|
||||
|
||||
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
|
||||
let _ = HTTP_CLIENT
|
||||
let _ = http_client
|
||||
.post(&new_user_webhook)
|
||||
.json(&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "new_user"}))
|
||||
.send()
|
||||
@@ -1561,19 +1565,21 @@ async fn login(
|
||||
cookies: Cookies,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(argon2): Extension<Arc<Argon2<'_>>>,
|
||||
Extension(is_secure): Extension<Arc<IsSecure>>,
|
||||
Extension(cookie_domain): Extension<Arc<CookieDomain>>,
|
||||
Json(Login { email, password }): Json<Login>,
|
||||
) -> Result<String> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let email_w_h: Option<(String, String, bool, bool)> = sqlx::query_as(
|
||||
"SELECT email, password_hash, super_admin, first_time_user FROM password WHERE email = $1 AND login_type = \
|
||||
let email_w_h: Option<(String, String, bool)> = sqlx::query_as(
|
||||
"SELECT email, password_hash, super_admin FROM password WHERE email = $1 AND login_type = \
|
||||
'password'",
|
||||
)
|
||||
.bind(&email)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some((email, hash, super_admin, first_time_user)) = email_w_h {
|
||||
if let Some((email, hash, super_admin)) = email_w_h {
|
||||
let parsed_hash =
|
||||
PasswordHash::new(&hash).map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
if argon2
|
||||
@@ -1582,27 +1588,15 @@ async fn login(
|
||||
{
|
||||
Err(Error::BadRequest("Invalid login".to_string()))
|
||||
} else {
|
||||
if first_time_user {
|
||||
sqlx::query_scalar!(
|
||||
"UPDATE password SET first_time_user = false WHERE email = $1",
|
||||
&email
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
let mut c = Cookie::new("first_time", "1");
|
||||
if let Some(domain) = COOKIE_DOMAIN.as_ref() {
|
||||
c.set_domain(domain);
|
||||
}
|
||||
c.set_secure(false);
|
||||
c.set_expires(time::OffsetDateTime::now_utc() + time::Duration::minutes(15));
|
||||
c.set_http_only(false);
|
||||
c.set_path("/");
|
||||
|
||||
cookies.add(c);
|
||||
}
|
||||
|
||||
let token = create_session_token(&email, super_admin, &mut tx, cookies).await?;
|
||||
|
||||
let token = create_session_token(
|
||||
&email,
|
||||
super_admin,
|
||||
&mut tx,
|
||||
cookies,
|
||||
is_secure.0,
|
||||
&cookie_domain.as_ref().0,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
Ok(token)
|
||||
}
|
||||
@@ -1616,6 +1610,8 @@ pub async fn create_session_token<'c>(
|
||||
super_admin: bool,
|
||||
tx: &mut sqlx::Transaction<'c, sqlx::Postgres>,
|
||||
cookies: Cookies,
|
||||
is_secure: bool,
|
||||
domain: &Option<String>,
|
||||
) -> Result<String> {
|
||||
let token = rd_string(30);
|
||||
sqlx::query!(
|
||||
@@ -1631,12 +1627,12 @@ pub async fn create_session_token<'c>(
|
||||
.execute(tx)
|
||||
.await?;
|
||||
let mut cookie = Cookie::new(COOKIE_NAME, token.clone());
|
||||
cookie.set_secure(*IS_SECURE);
|
||||
cookie.set_same_site(Some(cookie::SameSite::Lax));
|
||||
cookie.set_secure(is_secure);
|
||||
cookie.set_same_site(cookie::SameSite::Lax);
|
||||
cookie.set_http_only(true);
|
||||
cookie.set_path(COOKIE_PATH);
|
||||
if COOKIE_DOMAIN.is_some() {
|
||||
cookie.set_domain(COOKIE_DOMAIN.clone().unwrap());
|
||||
if domain.is_some() {
|
||||
cookie.set_domain(domain.clone().unwrap());
|
||||
}
|
||||
let mut expire: OffsetDateTime = time::OffsetDateTime::now_utc();
|
||||
expire += time::Duration::days(3);
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
oauth2::_refresh_token,
|
||||
oauth2::{AllClients, _refresh_token},
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
BaseUrl,
|
||||
};
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
@@ -34,6 +37,7 @@ use windmill_common::{
|
||||
};
|
||||
|
||||
use magic_crypt::{MagicCrypt256, MagicCryptTrait};
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use sqlx::{Postgres, Transaction};
|
||||
|
||||
@@ -50,6 +54,7 @@ pub fn workspaced_service() -> Router {
|
||||
|
||||
async fn list_contextual_variables(
|
||||
Path(w_id): Path<String>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
Authed { username, email, .. }: Authed,
|
||||
) -> JsonResult<Vec<ContextualVariable>> {
|
||||
Ok(Json(
|
||||
@@ -60,6 +65,7 @@ async fn list_contextual_variables(
|
||||
&username,
|
||||
"017e0ad5-f499-73b6-5488-92a61c5196dd",
|
||||
format!("u/{username}").as_str(),
|
||||
&base_url.0,
|
||||
Some("u/user/script_path".to_string()),
|
||||
Some("017e0ad5-f499-73b6-5488-92a61c5196dd".to_string()),
|
||||
Some("u/user/encapsulating_flow_path".to_string()),
|
||||
@@ -85,7 +91,7 @@ async fn list_variables(
|
||||
from variable
|
||||
LEFT JOIN account ON variable.account = account.id AND account.workspace_id = variable.workspace_id
|
||||
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = variable.workspace_id
|
||||
WHERE variable.workspace_id = $1 ORDER BY path",
|
||||
WHERE variable.workspace_id = $1 OR (is_secret IS NOT TRUE AND variable.workspace_id = 'starter') ORDER BY path",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_all(&mut tx)
|
||||
@@ -105,6 +111,8 @@ async fn get_variable(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Query(q): Query<GetVariableQuery>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Extension(clients): Extension<Arc<AllClients>>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
) -> JsonResult<ListableVariable> {
|
||||
let path = path.to_path();
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
@@ -116,7 +124,8 @@ async fn get_variable(
|
||||
from variable
|
||||
LEFT JOIN account ON variable.account = account.id
|
||||
LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = variable.workspace_id
|
||||
WHERE variable.path = $1 AND variable.workspace_id = $2
|
||||
WHERE variable.path = $1 AND (variable.workspace_id = $2 OR (is_secret IS NOT TRUE AND \
|
||||
variable.workspace_id = 'starter'))
|
||||
LIMIT 1",
|
||||
)
|
||||
.bind(&path)
|
||||
@@ -142,7 +151,17 @@ async fn get_variable(
|
||||
let value = variable.value.unwrap_or_else(|| "".to_string());
|
||||
ListableVariable {
|
||||
value: if variable.is_expired.unwrap_or(false) && variable.account.is_some() {
|
||||
Some(_refresh_token(tx, &variable.path, w_id, variable.account.unwrap()).await?)
|
||||
Some(
|
||||
_refresh_token(
|
||||
tx,
|
||||
&variable.path,
|
||||
w_id,
|
||||
variable.account.unwrap(),
|
||||
clients,
|
||||
http_client,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
} else if !value.is_empty() && decrypt_secret {
|
||||
let mc = build_crypt(&mut tx, &w_id).await?;
|
||||
tx.commit().await?;
|
||||
@@ -208,13 +227,12 @@ async fn create_variable(
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(AlreadyEncrypted { already_encrypted }): Query<AlreadyEncrypted>,
|
||||
Json(variable): Json<CreateVariable>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
check_path_conflict(&mut tx, &w_id, &variable.path).await?;
|
||||
let value = if variable.is_secret && !already_encrypted.unwrap_or(false) {
|
||||
let value = if variable.is_secret {
|
||||
let mc = build_crypt(&mut tx, &w_id).await?;
|
||||
encrypt(&mc, &variable.value)
|
||||
} else {
|
||||
@@ -312,18 +330,12 @@ struct EditVariable {
|
||||
description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AlreadyEncrypted {
|
||||
already_encrypted: Option<bool>,
|
||||
}
|
||||
|
||||
async fn update_variable(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Query(AlreadyEncrypted { already_encrypted }): Query<AlreadyEncrypted>,
|
||||
Json(ns): Json<EditVariable>,
|
||||
) -> Result<String> {
|
||||
use sql_builder::prelude::*;
|
||||
@@ -349,7 +361,7 @@ async fn update_variable(
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
let value = if is_secret && !already_encrypted.unwrap_or(false) {
|
||||
let value = if is_secret {
|
||||
let mc = build_crypt(&mut tx, &w_id).await?;
|
||||
encrypt(&mc, &nvalue)
|
||||
} else {
|
||||
|
||||
@@ -24,7 +24,6 @@ pub enum WebhookMessage {
|
||||
CreateFlow { workspace: String, path: String },
|
||||
UpdateFlow { workspace: String, old_path: String, new_path: String },
|
||||
ArchiveFlow { workspace: String, path: String },
|
||||
DeleteFlow { workspace: String, path: String },
|
||||
CreateFolder { workspace: String, name: String },
|
||||
UpdateFolder { workspace: String, name: String },
|
||||
DeleteFolder { workspace: String, name: String },
|
||||
|
||||
@@ -6,31 +6,25 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(enterprise)]
|
||||
use std::str::FromStr;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
#[cfg(enterprise)]
|
||||
use crate::BASE_URL;
|
||||
use crate::{
|
||||
apps::AppWithLastVersion,
|
||||
db::{UserDB, DB},
|
||||
folders::Folder,
|
||||
resources::{Resource, ResourceType},
|
||||
users::{Authed, WorkspaceInvite, NEW_USER_WEBHOOK},
|
||||
utils::require_super_admin,
|
||||
HTTP_CLIENT,
|
||||
BaseUrl,
|
||||
};
|
||||
#[cfg(enterprise)]
|
||||
use axum::response::Redirect;
|
||||
use axum::{
|
||||
body::StreamBody,
|
||||
extract::{Extension, Path, Query},
|
||||
headers,
|
||||
response::IntoResponse,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
#[cfg(enterprise)]
|
||||
use reqwest::Client;
|
||||
use stripe::CustomerId;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
@@ -49,7 +43,7 @@ use tokio::fs::File;
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
let router = Router::new()
|
||||
Router::new()
|
||||
.route("/list_pending_invites", get(list_pending_invites))
|
||||
.route("/update", post(edit_workspace))
|
||||
.route("/archive", post(archive_workspace))
|
||||
@@ -61,16 +55,9 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/edit_webhook", post(edit_webhook))
|
||||
.route("/edit_auto_invite", post(edit_auto_invite))
|
||||
.route("/tarball", get(tarball_workspace))
|
||||
.route("/premium_info", get(premium_info));
|
||||
|
||||
#[cfg(enterprise)]
|
||||
let router = {
|
||||
router
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal));
|
||||
};
|
||||
|
||||
router
|
||||
.route("/premium_info", get(premium_info))
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal))
|
||||
}
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
@@ -230,25 +217,24 @@ async fn premium_info(
|
||||
Ok(Json(row))
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[derive(Deserialize)]
|
||||
struct PlanQuery {
|
||||
plan: String,
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
async fn stripe_checkout(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
Query(plan): Query<PlanQuery>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
) -> Result<Redirect> {
|
||||
// #[cfg(feature = "enterprise")]
|
||||
{
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
|
||||
let success_rd = format!("{}/workspace_settings/checkout?success=true", *BASE_URL);
|
||||
let failure_rd = format!("{}/workspace_settings/checkout?success=false", *BASE_URL);
|
||||
let success_rd = format!("{}/workspace_settings/checkout?success=true", base_url.0);
|
||||
let failure_rd = format!("{}/workspace_settings/checkout?success=false", base_url.0);
|
||||
let checkout_session = {
|
||||
let mut params = stripe::CreateCheckoutSession::new(&failure_rd, &success_rd);
|
||||
params.mode = Some(stripe::CheckoutSessionMode::Subscription);
|
||||
@@ -302,11 +288,11 @@ async fn stripe_checkout(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
async fn stripe_portal(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(base_url): Extension<Arc<BaseUrl>>,
|
||||
) -> Result<Redirect> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
let customer_id = sqlx::query_scalar!(
|
||||
@@ -317,7 +303,7 @@ async fn stripe_portal(
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("no customer id for workspace {}", w_id)))?;
|
||||
let client = stripe::Client::new(std::env::var("STRIPE_KEY").expect("STRIPE_KEY"));
|
||||
let success_rd = format!("{}/workspace_settings?tab=premium", *BASE_URL);
|
||||
let success_rd = format!("{}/workspace_settings?tab=premium", base_url.0);
|
||||
let portal_session = {
|
||||
let customer_id = CustomerId::from_str(&customer_id).unwrap();
|
||||
let mut params = stripe::CreateBillingPortalSession::new(customer_id);
|
||||
@@ -676,19 +662,19 @@ async fn create_workspace(
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
// let mc = magic_crypt::new_magic_crypt!(key, 256);
|
||||
// sqlx::query!(
|
||||
// "INSERT INTO variable
|
||||
// (workspace_id, path, value, is_secret, description)
|
||||
// VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
|
||||
// ($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
|
||||
// nw.id,
|
||||
// crate::variables::encrypt(&mc, "pretty secret value"),
|
||||
// nw.id,
|
||||
// "finland does not actually exist",
|
||||
// )
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
let mc = magic_crypt::new_magic_crypt!(key, 256);
|
||||
sqlx::query!(
|
||||
"INSERT INTO variable
|
||||
(workspace_id, path, value, is_secret, description)
|
||||
VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
|
||||
($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
|
||||
nw.id,
|
||||
crate::variables::encrypt(&mc, "pretty secret value"),
|
||||
nw.id,
|
||||
"finland does not actually exist",
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr
|
||||
@@ -940,6 +926,7 @@ pub async fn invite_user_to_all_auto_invite_worspaces(db: &DB, email: &str) -> R
|
||||
async fn invite_user(
|
||||
Authed { username, is_admin, .. }: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(http_client): Extension<Client>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(nu): Json<NewWorkspaceInvite>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -962,7 +949,7 @@ async fn invite_user(
|
||||
tx.commit().await?;
|
||||
|
||||
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
|
||||
let _ = &HTTP_CLIENT
|
||||
let _ = http_client
|
||||
.post(&new_user_webhook)
|
||||
.json(&serde_json::json!({"email" : &nu.email, "event": "new_invite"}))
|
||||
.send()
|
||||
@@ -1065,118 +1052,20 @@ struct ScriptMetadata {
|
||||
lock: Vec<String>,
|
||||
}
|
||||
|
||||
enum ArchiveImpl {
|
||||
Zip(async_zip::write::ZipFileWriter<File>),
|
||||
Tar(tokio_tar::Builder<File>),
|
||||
}
|
||||
|
||||
impl ArchiveImpl {
|
||||
async fn write_to_archive(&mut self, content: &str, path: &str) -> Result<()> {
|
||||
match self {
|
||||
ArchiveImpl::Tar(t) => {
|
||||
let bytes = content.as_bytes();
|
||||
let mut header = tokio_tar::Header::new_gnu();
|
||||
header.set_size(bytes.len() as u64);
|
||||
header.set_mtime(0);
|
||||
header.set_uid(0);
|
||||
header.set_gid(0);
|
||||
header.set_mode(0o777);
|
||||
header.set_cksum();
|
||||
t.append_data(&mut header, path, bytes).await?;
|
||||
}
|
||||
ArchiveImpl::Zip(z) => {
|
||||
let header = async_zip::ZipEntryBuilder::new(
|
||||
path.to_owned(),
|
||||
async_zip::Compression::Deflate,
|
||||
)
|
||||
.last_modification_date(Default::default())
|
||||
.unix_permissions(0o777)
|
||||
.build();
|
||||
z.write_entry_whole(header, content.as_bytes())
|
||||
.await
|
||||
.map_err(to_anyhow)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn finish(self) -> Result<()> {
|
||||
match self {
|
||||
ArchiveImpl::Tar(t) => t.into_inner().await?,
|
||||
ArchiveImpl::Zip(z) => z.close().await.map_err(to_anyhow)?,
|
||||
}
|
||||
.sync_all()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ArchiveQueryParams {
|
||||
archive_type: Option<String>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_string_without_metadata<T>(value: &T, preserve_extra_perms: bool) -> Result<String>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
let value = serde_json::to_value(value).map_err(to_anyhow)?;
|
||||
value
|
||||
.as_object()
|
||||
.map(|obj| {
|
||||
let mut obj = obj.clone();
|
||||
for key in [
|
||||
"workspace_id",
|
||||
"path",
|
||||
"name",
|
||||
"versions",
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"edited_at",
|
||||
"edited_by",
|
||||
"archived",
|
||||
] {
|
||||
if obj.contains_key(key) {
|
||||
obj.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
if !preserve_extra_perms && obj.contains_key("extra_perms") {
|
||||
obj.remove("extra_perms");
|
||||
}
|
||||
|
||||
serde_json::to_string_pretty(&obj).ok()
|
||||
})
|
||||
.flatten()
|
||||
.ok_or_else(|| Error::BadRequest("Impossible to serialize value".to_string()))
|
||||
}
|
||||
|
||||
async fn tarball_workspace(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Query(ArchiveQueryParams { archive_type }): Query<ArchiveQueryParams>,
|
||||
) -> Result<([(headers::HeaderName, String); 2], impl IntoResponse)> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
|
||||
let tmp_dir = TempDir::new_in(".")?;
|
||||
|
||||
let name = match archive_type.as_deref() {
|
||||
Some("tar") | None => Ok(format!("windmill-{w_id}.tar")),
|
||||
Some("zip") => Ok(format!("windmill-{w_id}.zip")),
|
||||
Some(t) => Err(Error::BadRequest(format!("Invalid Archive Type {t}"))),
|
||||
}?;
|
||||
let name = format!("windmill-{w_id}.tar");
|
||||
let file_path = tmp_dir.path().join(&name);
|
||||
let file = File::create(&file_path).await?;
|
||||
let mut archive = match archive_type.as_deref() {
|
||||
Some("tar") | None => Ok(ArchiveImpl::Tar(tokio_tar::Builder::new(file))),
|
||||
Some("zip") => Ok(ArchiveImpl::Zip(async_zip::write::ZipFileWriter::new(file))),
|
||||
Some(t) => Err(Error::BadRequest(format!("Invalid Archive Type {t}"))),
|
||||
}?;
|
||||
let mut a = tokio_tar::Builder::new(file);
|
||||
|
||||
{
|
||||
let folders = sqlx::query_as::<_, Folder>("SELECT * FROM folder WHERE workspace_id = $1")
|
||||
.bind(&w_id)
|
||||
@@ -1184,12 +1073,12 @@ async fn tarball_workspace(
|
||||
.await?;
|
||||
|
||||
for folder in folders {
|
||||
archive
|
||||
.write_to_archive(
|
||||
&to_string_without_metadata(&folder, true).unwrap(),
|
||||
&format!("f/{}/folder.meta.json", folder.name),
|
||||
)
|
||||
.await?;
|
||||
write_to_archive(
|
||||
serde_json::to_string_pretty(&folder).unwrap(),
|
||||
format!("f/{}/folder.meta.json", folder.name),
|
||||
&mut a,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,9 +1099,7 @@ async fn tarball_workspace(
|
||||
ScriptLang::Go => "go",
|
||||
ScriptLang::Bash => "sh",
|
||||
};
|
||||
archive
|
||||
.write_to_archive(&script.content, &format!("{}.{}", script.path, ext))
|
||||
.await?;
|
||||
write_to_archive(script.content, format!("{}.{}", script.path, ext), &mut a).await?;
|
||||
|
||||
let lock = script
|
||||
.lock
|
||||
@@ -1228,9 +1115,7 @@ async fn tarball_workspace(
|
||||
lock,
|
||||
};
|
||||
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();
|
||||
archive
|
||||
.write_to_archive(&metadata_str, &format!("{}.script.json", script.path))
|
||||
.await?;
|
||||
write_to_archive(metadata_str, format!("{}.script.json", script.path), &mut a).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1244,10 +1129,13 @@ async fn tarball_workspace(
|
||||
.await?;
|
||||
|
||||
for resource in resources {
|
||||
let resource_str = &to_string_without_metadata(&resource, false).unwrap();
|
||||
archive
|
||||
.write_to_archive(&resource_str, &format!("{}.resource.json", resource.path))
|
||||
.await?;
|
||||
let resource_str = serde_json::to_string_pretty(&resource).unwrap();
|
||||
write_to_archive(
|
||||
resource_str,
|
||||
format!("{}.resource.json", resource.path),
|
||||
&mut a,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1261,13 +1149,13 @@ async fn tarball_workspace(
|
||||
.await?;
|
||||
|
||||
for resource_type in resource_types {
|
||||
let resource_str = &to_string_without_metadata(&resource_type, false).unwrap();
|
||||
archive
|
||||
.write_to_archive(
|
||||
&resource_str,
|
||||
&format!("{}.resource-type.json", resource_type.name),
|
||||
)
|
||||
.await?;
|
||||
let resource_str = serde_json::to_string_pretty(&resource_type).unwrap();
|
||||
write_to_archive(
|
||||
resource_str,
|
||||
format!("{}.resource-type.json", resource_type.name),
|
||||
&mut a,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1280,49 +1168,25 @@ async fn tarball_workspace(
|
||||
.await?;
|
||||
|
||||
for flow in flows {
|
||||
let flow_str = &to_string_without_metadata(&flow, false).unwrap();
|
||||
archive
|
||||
.write_to_archive(&flow_str, &format!("{}.flow.json", flow.path))
|
||||
.await?;
|
||||
let flow_str = serde_json::to_string_pretty(&flow).unwrap();
|
||||
write_to_archive(flow_str, format!("{}.flow.json", flow.path), &mut a).await?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let variables = sqlx::query_as::<_, ExportableListableVariable>(
|
||||
"SELECT *, false as is_expired FROM variable WHERE workspace_id = $1",
|
||||
"SELECT *, false as is_expired FROM variable WHERE workspace_id = $1 AND is_secret = false",
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
for var in variables {
|
||||
let var_str = &to_string_without_metadata(&var, false).unwrap();
|
||||
archive
|
||||
.write_to_archive(&var_str, &format!("{}.variable.json", var.path))
|
||||
.await?;
|
||||
let flow_str = serde_json::to_string_pretty(&var).unwrap();
|
||||
write_to_archive(flow_str, format!("{}.variable.json", var.path), &mut a).await?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let apps = sqlx::query_as!(
|
||||
AppWithLastVersion,
|
||||
"SELECT app.id, app.path, app.summary, app.versions, app.policy,
|
||||
app.extra_perms, app_version.value,
|
||||
app_version.created_at, app_version.created_by from app, app_version
|
||||
WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)]",
|
||||
&w_id
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
for app in apps {
|
||||
let app_str = &to_string_without_metadata(&app, false).unwrap();
|
||||
archive
|
||||
.write_to_archive(&app_str, &format!("{}.app.json", app.path))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
archive.finish().await?;
|
||||
a.into_inner().await?;
|
||||
|
||||
let file = tokio::fs::File::open(file_path).await?;
|
||||
|
||||
@@ -1339,3 +1203,20 @@ async fn tarball_workspace(
|
||||
|
||||
Ok((headers, body))
|
||||
}
|
||||
|
||||
async fn write_to_archive(
|
||||
content: String,
|
||||
path: String,
|
||||
a: &mut tokio_tar::Builder<File>,
|
||||
) -> Result<()> {
|
||||
let bytes = content.as_bytes();
|
||||
let mut header = tokio_tar::Header::new_gnu();
|
||||
header.set_size(bytes.len() as u64);
|
||||
header.set_mtime(0);
|
||||
header.set_uid(0);
|
||||
header.set_gid(0);
|
||||
header.set_mode(0o777);
|
||||
header.set_cksum();
|
||||
a.append_data(&mut header, path, bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -41,4 +41,3 @@ hyper = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
reqwest = { workspace = true, optional = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
lazy_static.workspace = true
|
||||
@@ -59,7 +59,6 @@ pub struct NewFlow {
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub struct FlowValue {
|
||||
pub modules: Vec<FlowModule>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub failure_module: Option<FlowModule>,
|
||||
#[serde(default)]
|
||||
@@ -153,9 +152,7 @@ pub struct FlowModule {
|
||||
#[serde(alias = "input_transform")]
|
||||
pub input_transforms: HashMap<String, InputTransform>,
|
||||
pub value: FlowModuleValue,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stop_after_if: Option<StopAfterIf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub summary: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suspend: Option<Suspend>,
|
||||
@@ -248,9 +245,7 @@ pub enum FlowModuleValue {
|
||||
#[serde(alias = "input_transform")]
|
||||
input_transforms: HashMap<String, InputTransform>,
|
||||
content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
lock: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
path: Option<String>,
|
||||
language: ScriptLang,
|
||||
},
|
||||
|
||||
@@ -26,13 +26,12 @@ pub mod variables;
|
||||
#[cfg(feature = "tracing_init")]
|
||||
pub mod tracing_init;
|
||||
|
||||
pub const DEFAULT_NUM_WORKERS: usize = 3;
|
||||
pub const DEFAULT_TIMEOUT: i32 = 300;
|
||||
pub const DEFAULT_SLEEP_QUEUE: u64 = 50;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50;
|
||||
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 3;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref BASE_URL: String = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio")]
|
||||
pub async fn shutdown_signal(tx: tokio::sync::broadcast::Sender<()>) -> anyhow::Result<()> {
|
||||
use std::io;
|
||||
@@ -124,8 +123,10 @@ pub async fn get_latest_hash_for_path<'c>(
|
||||
script_path: &str,
|
||||
) -> error::Result<scripts::ScriptHash> {
|
||||
let script_hash_o = sqlx::query_scalar!(
|
||||
"select hash from script where path = $1 AND workspace_id = $2 AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND workspace_id = $2) AND
|
||||
"select hash from script where path = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter') AND
|
||||
created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND (workspace_id = $2 OR \
|
||||
workspace_id = 'starter')) AND
|
||||
deleted = false",
|
||||
script_path,
|
||||
w_id
|
||||
|
||||
@@ -210,7 +210,7 @@ pub fn to_hex_string(i: &i64) -> String {
|
||||
pub async fn get_hub_script_by_path(
|
||||
email: &str,
|
||||
path: StripPath,
|
||||
http_client: &reqwest::Client,
|
||||
http_client: reqwest::Client,
|
||||
) -> crate::error::Result<String> {
|
||||
use crate::{
|
||||
error::{to_anyhow, Error},
|
||||
@@ -239,7 +239,7 @@ pub async fn get_hub_script_by_path(
|
||||
pub async fn get_full_hub_script_by_path(
|
||||
email: &str,
|
||||
path: StripPath,
|
||||
http_client: &reqwest::Client,
|
||||
http_client: reqwest::Client,
|
||||
) -> crate::error::Result<HubScript> {
|
||||
use crate::{
|
||||
error::{to_anyhow, Error},
|
||||
|
||||
@@ -74,7 +74,7 @@ pub fn not_found_if_none<T, U: AsRef<str>>(opt: Option<T>, kind: &str, name: U)
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub async fn list_elems_from_hub(
|
||||
http_client: &reqwest::Client,
|
||||
http_client: reqwest::Client,
|
||||
url: &str,
|
||||
email: &str,
|
||||
) -> Result<serde_json::Value> {
|
||||
@@ -88,7 +88,7 @@ pub async fn list_elems_from_hub(
|
||||
|
||||
#[cfg(feature = "reqwest")]
|
||||
pub async fn http_get_from_hub(
|
||||
http_client: &reqwest::Client,
|
||||
http_client: reqwest::Client,
|
||||
url: &str,
|
||||
email: &str,
|
||||
plain: bool,
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::BASE_URL;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
|
||||
pub struct ContextualVariable {
|
||||
@@ -68,6 +66,7 @@ pub fn get_reserved_variables(
|
||||
username: &str,
|
||||
job_id: &str,
|
||||
permissioned_as: &str,
|
||||
base_url: &str,
|
||||
path: Option<String>,
|
||||
flow_id: Option<String>,
|
||||
flow_path: Option<String>,
|
||||
@@ -115,7 +114,7 @@ pub fn get_reserved_variables(
|
||||
},
|
||||
ContextualVariable {
|
||||
name: "WM_BASE_URL".to_string(),
|
||||
value: BASE_URL.clone(),
|
||||
value: base_url.to_string(),
|
||||
description: "base url of this instance".to_string(),
|
||||
},
|
||||
ContextualVariable {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Postgres, Transaction};
|
||||
use tracing::{instrument, Instrument};
|
||||
@@ -17,7 +16,7 @@ use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
error::{self, to_anyhow, Error},
|
||||
flow_status::{FlowStatus, JobResult, MAX_RETRY_ATTEMPTS, MAX_RETRY_INTERVAL},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue},
|
||||
scripts::{get_full_hub_script_by_path, HubScript, ScriptHash, ScriptLang},
|
||||
@@ -25,10 +24,6 @@ use windmill_common::{
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build().unwrap();
|
||||
|
||||
// TODO: these aren't synced, they should be moved into the queue abstraction once/if that happens.
|
||||
static ref QUEUE_PUSH_COUNT: prometheus::IntCounter = prometheus::register_int_counter!(
|
||||
"queue_push_count",
|
||||
@@ -389,7 +384,8 @@ pub async fn push<'c>(
|
||||
match job_payload {
|
||||
JobPayload::ScriptHash { hash, path } => {
|
||||
let language = sqlx::query_scalar!(
|
||||
"SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
"SELECT language as \"language: ScriptLang\" FROM script WHERE hash = $1 AND \
|
||||
(workspace_id = $2 OR workspace_id = 'starter')",
|
||||
hash.0,
|
||||
workspace_id
|
||||
)
|
||||
@@ -410,7 +406,7 @@ pub async fn push<'c>(
|
||||
)
|
||||
}
|
||||
JobPayload::ScriptHub { path } => {
|
||||
let script = get_hub_script(&HTTP_CLIENT, path.clone(), email)
|
||||
let script = get_hub_script(path.clone(), email)
|
||||
.await
|
||||
.context("error fetching hub script")?;
|
||||
(
|
||||
@@ -440,10 +436,11 @@ pub async fn push<'c>(
|
||||
),
|
||||
JobPayload::FlowDependencies { path } => {
|
||||
let value_json = sqlx::query_scalar!(
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
workspace_id
|
||||
)
|
||||
"SELECT value FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter')",
|
||||
path,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("not found flow at path {:?}", path)))?;
|
||||
@@ -466,7 +463,8 @@ pub async fn push<'c>(
|
||||
}
|
||||
JobPayload::Flow(flow) => {
|
||||
let value_json = sqlx::query_scalar!(
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
"SELECT value FROM flow WHERE path = $1 AND (workspace_id = $2 OR workspace_id = \
|
||||
'starter')",
|
||||
flow,
|
||||
workspace_id
|
||||
)
|
||||
@@ -612,14 +610,17 @@ pub fn canceled_job_to_result(job: &QueuedJob) -> serde_json::Value {
|
||||
serde_json::json!({"message": format!("Job canceled: {reason} by {canceler}"), "name": "Canceled", "reason": reason, "canceler": canceler})
|
||||
}
|
||||
|
||||
pub async fn get_hub_script(
|
||||
client: &reqwest::Client,
|
||||
path: String,
|
||||
email: &str,
|
||||
) -> error::Result<HubScript> {
|
||||
get_full_hub_script_by_path(email, StripPath(path), client)
|
||||
.await
|
||||
.map(|e| e)
|
||||
pub async fn get_hub_script(path: String, email: &str) -> error::Result<HubScript> {
|
||||
get_full_hub_script_by_path(
|
||||
email,
|
||||
StripPath(path),
|
||||
reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?,
|
||||
)
|
||||
.await
|
||||
.map(|e| e)
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow, Serialize, Clone)]
|
||||
|
||||
@@ -3,10 +3,11 @@ name = "windmill-worker"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
default-run = "worker"
|
||||
|
||||
[lib]
|
||||
name = "windmill_worker"
|
||||
path = "src/lib.rs"
|
||||
[[bin]]
|
||||
name = "worker"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -34,11 +34,10 @@ pub async fn eval_timeout(
|
||||
env: Vec<(String, serde_json::Value)>,
|
||||
creds: Option<EvalCreds>,
|
||||
by_id: Option<IdContext>,
|
||||
base_internal_url: &str,
|
||||
base_internal_url: String,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let expr2 = expr.clone();
|
||||
let (sender, mut receiver) = oneshot::channel::<IsolateHandle>();
|
||||
let base_internal_url: String = base_internal_url.to_string();
|
||||
timeout(
|
||||
std::time::Duration::from_millis(2000),
|
||||
tokio::task::spawn_blocking(move || {
|
||||
@@ -347,7 +346,7 @@ mod tests {
|
||||
let code = "value.test + params.test";
|
||||
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions::default());
|
||||
let res = eval(&mut runtime, code, env, None, None, String::new().as_str()).await?;
|
||||
let res = eval(&mut runtime, code, env, None, None, "").await?;
|
||||
assert_eq!(res, json!(4));
|
||||
Ok(())
|
||||
}
|
||||
@@ -360,7 +359,7 @@ mod tests {
|
||||
multiline template`";
|
||||
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions::default());
|
||||
let res = eval(&mut runtime, code, env, None, None, String::new().as_str()).await?;
|
||||
let res = eval(&mut runtime, code, env, None, None, "").await?;
|
||||
assert_eq!(res, json!("my 5\nmultiline template"));
|
||||
Ok(())
|
||||
}
|
||||
@@ -373,7 +372,7 @@ multiline template`";
|
||||
];
|
||||
let code = r#"params.test"#;
|
||||
|
||||
let res = eval_timeout(code.to_string(), env, None, None, String::new().as_str()).await?;
|
||||
let res = eval_timeout(code.to_string(), env, None, None, "".to_string()).await?;
|
||||
assert_eq!(res, json!(2));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
138
backend/windmill-worker/src/main.rs
Normal file
138
backend/windmill-worker/src/main.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2022
|
||||
* This file and its contents are licensed under the AGPLv3 License.
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
|
||||
use anyhow::Context;
|
||||
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
utils::rd_string,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// dotenv().ok();
|
||||
|
||||
windmill_common::tracing_init::initialize_tracing();
|
||||
|
||||
let db = async {
|
||||
let database_url = std::env::var("DATABASE_URL")
|
||||
.map_err(|_| Error::BadConfig("DATABASE_URL env var is missing".to_string()))?;
|
||||
|
||||
let max_connections = match std::env::var("DATABASE_CONNECTIONS") {
|
||||
Ok(n) => n.parse::<u32>().context("invalid DATABASE_CONNECTIONS")?,
|
||||
Err(_) => 10,
|
||||
};
|
||||
|
||||
Ok::<Pool<Postgres>, error::Error>(
|
||||
PgPoolOptions::new()
|
||||
.max_connections(max_connections)
|
||||
.max_lifetime(Duration::from_secs(30 * 60)) // 30 mins
|
||||
.connect(&database_url)
|
||||
.await
|
||||
.map_err(|err| Error::ConnectingToDatabase(err.to_string()))?,
|
||||
)
|
||||
}
|
||||
.await?;
|
||||
|
||||
let metrics_addr: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
.ok()
|
||||
.map(|s| {
|
||||
s.parse::<bool>()
|
||||
.map(|b| b.then(|| SocketAddr::from(([0, 0, 0, 0], 8001))))
|
||||
.or_else(|_| s.parse::<SocketAddr>().map(Some))
|
||||
})
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
let shutdown_signal = windmill_common::shutdown_signal(tx);
|
||||
|
||||
let base_internal_url =
|
||||
std::env::var("BASE_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
|
||||
|
||||
let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string());
|
||||
|
||||
let timeout = std::env::var("TIMEOUT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i32>().ok())
|
||||
.unwrap_or(windmill_common::DEFAULT_TIMEOUT);
|
||||
|
||||
let workers_f = async {
|
||||
let sleep_queue = std::env::var("SLEEP_QUEUE")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
.unwrap_or(windmill_common::DEFAULT_SLEEP_QUEUE);
|
||||
let disable_nuser = std::env::var("DISABLE_NUSER")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
let disable_nsjail = std::env::var("DISABLE_NSJAIL")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(true);
|
||||
let keep_job_dir = std::env::var("KEEP_JOB_DIR")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
let sync_bucket = std::env::var("S3_CACHE_BUCKET")
|
||||
.ok()
|
||||
.map(|e| Some(e))
|
||||
.unwrap_or(None);
|
||||
|
||||
tracing::info!(
|
||||
"DISABLE_NSJAIL: {disable_nsjail}, DISABLE_NUSER: {disable_nuser}, BASE_URL: \
|
||||
{base_url}, SLEEP_QUEUE: {sleep_queue}, TIMEOUT: \
|
||||
{timeout}, KEEP_JOB_DIR: {keep_job_dir}"
|
||||
);
|
||||
let instance_name = rd_string(5);
|
||||
|
||||
let ip = windmill_common::external_ip::get_ip()
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!(error = e.to_string(), "failed to get external IP");
|
||||
"unretrievable IP".to_string()
|
||||
});
|
||||
let worker_name = format!("dt-worker-{}-{}", &instance_name, rd_string(5));
|
||||
windmill_worker::run_worker(
|
||||
&db.clone(),
|
||||
timeout,
|
||||
&instance_name,
|
||||
worker_name,
|
||||
1,
|
||||
1,
|
||||
&ip,
|
||||
sleep_queue,
|
||||
windmill_worker::WorkerConfig {
|
||||
disable_nsjail,
|
||||
disable_nuser,
|
||||
base_internal_url,
|
||||
base_url,
|
||||
keep_job_dir,
|
||||
},
|
||||
sync_bucket,
|
||||
rx.resubscribe(),
|
||||
)
|
||||
.await;
|
||||
Ok(()) as anyhow::Result<()>
|
||||
};
|
||||
|
||||
let metrics_f = async {
|
||||
match metrics_addr {
|
||||
Some(addr) => windmill_common::serve_metrics(addr, rx.resubscribe())
|
||||
.await
|
||||
.map_err(anyhow::Error::from),
|
||||
None => Ok(()),
|
||||
}
|
||||
};
|
||||
|
||||
futures::try_join!(shutdown_signal, workers_f, metrics_f)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
* Copyright: Windmill Labs, Inc 2022
|
||||
* This file and its contents are licensed under the AGPLv3 License.
|
||||
* Please see the included NOTICE for copyright information and
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
// use std::{net::SocketAddr, time::Duration};
|
||||
|
||||
// use anyhow::Context;
|
||||
// use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
|
||||
// use windmill_common::{
|
||||
// error::{self, Error},
|
||||
// utils::rd_string,
|
||||
// };
|
||||
|
||||
// #[tokio::main]
|
||||
// async fn main() -> anyhow::Result<()> {
|
||||
// // dotenv().ok();
|
||||
|
||||
// windmill_common::tracing_init::initialize_tracing();
|
||||
|
||||
// let db = async {
|
||||
// let database_url = std::env::var("DATABASE_URL")
|
||||
// .map_err(|_| Error::BadConfig("DATABASE_URL env var is missing".to_string()))?;
|
||||
|
||||
// let max_connections = match std::env::var("DATABASE_CONNECTIONS") {
|
||||
// Ok(n) => n.parse::<u32>().context("invalid DATABASE_CONNECTIONS")?,
|
||||
// Err(_) => 10,
|
||||
// };
|
||||
|
||||
// Ok::<Pool<Postgres>, error::Error>(
|
||||
// PgPoolOptions::new()
|
||||
// .max_connections(max_connections)
|
||||
// .max_lifetime(Duration::from_secs(30 * 60)) // 30 mins
|
||||
// .connect(&database_url)
|
||||
// .await
|
||||
// .map_err(|err| Error::ConnectingToDatabase(err.to_string()))?,
|
||||
// )
|
||||
// }
|
||||
// .await?;
|
||||
|
||||
// let metrics_addr: Option<SocketAddr> = std::env::var("METRICS_ADDR")
|
||||
// .ok()
|
||||
// .map(|s| {
|
||||
// s.parse::<bool>()
|
||||
// .map(|b| b.then(|| SocketAddr::from(([0, 0, 0, 0], 8001))))
|
||||
// .or_else(|_| s.parse::<SocketAddr>().map(Some))
|
||||
// })
|
||||
// .transpose()?
|
||||
// .flatten();
|
||||
|
||||
// let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
|
||||
// let shutdown_signal = windmill_common::shutdown_signal(tx);
|
||||
|
||||
// let workers_f = async {
|
||||
// let instance_name = rd_string(5);
|
||||
|
||||
// let ip = windmill_common::external_ip::get_ip()
|
||||
// .await
|
||||
// .unwrap_or_else(|e| {
|
||||
// tracing::warn!(error = e.to_string(), "failed to get external IP");
|
||||
// "unretrievable IP".to_string()
|
||||
// });
|
||||
// let worker_name = format!("dt-worker-{}-{}", &instance_name, rd_string(5));
|
||||
// windmill_worker::run_worker(
|
||||
// &db.clone(),
|
||||
// &instance_name,
|
||||
// worker_name,
|
||||
// 1,
|
||||
// 1,
|
||||
// &ip,
|
||||
// rx.resubscribe(),
|
||||
// )
|
||||
// .await;
|
||||
// Ok(()) as anyhow::Result<()>
|
||||
// };
|
||||
|
||||
// let metrics_f = async {
|
||||
// match metrics_addr {
|
||||
// Some(addr) => windmill_common::serve_metrics(addr, rx.resubscribe())
|
||||
// .await
|
||||
// .map_err(anyhow::Error::from),
|
||||
// None => Ok(()),
|
||||
// }
|
||||
// };
|
||||
|
||||
// futures::try_join!(shutdown_signal, workers_f, metrics_f)?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ use std::time::Duration;
|
||||
|
||||
use crate::jobs::{add_completed_job, add_completed_job_error, schedule_again_if_scheduled};
|
||||
use crate::js_eval::{eval_timeout, EvalCreds, IdContext};
|
||||
use crate::{worker, KEEP_JOB_DIR};
|
||||
use crate::worker;
|
||||
use anyhow::Context;
|
||||
use async_recursion::async_recursion;
|
||||
use dyn_iter::DynIter;
|
||||
@@ -50,8 +50,9 @@ pub async fn update_flow_status_after_job_completion(
|
||||
unrecoverable: bool,
|
||||
same_worker_tx: Sender<Uuid>,
|
||||
worker_dir: &str,
|
||||
stop_early_override: Option<bool>,
|
||||
keep_job_dir: bool,
|
||||
base_internal_url: &str,
|
||||
stop_early_override: Option<bool>,
|
||||
) -> error::Result<()> {
|
||||
tracing::debug!("UPDATE FLOW STATUS: {flow:?} {success} {result:?} {w_id}");
|
||||
|
||||
@@ -120,7 +121,7 @@ pub async fn update_flow_status_after_job_completion(
|
||||
|
||||
let stop_early = success
|
||||
&& if let Some(expr) = r.stop_early_expr.clone() {
|
||||
compute_bool_from_expr(expr, &r.args, result.clone(), None, None, base_internal_url)
|
||||
compute_bool_from_expr(expr, &r.args, result.clone(), base_internal_url, None, None)
|
||||
.await?
|
||||
} else {
|
||||
false
|
||||
@@ -480,7 +481,7 @@ pub async fn update_flow_status_after_job_completion(
|
||||
};
|
||||
|
||||
if done {
|
||||
if flow_job.same_worker && !*KEEP_JOB_DIR {
|
||||
if flow_job.same_worker && !keep_job_dir {
|
||||
let _ = tokio::fs::remove_dir_all(format!("{worker_dir}/{}", flow_job.id)).await;
|
||||
}
|
||||
|
||||
@@ -497,12 +498,13 @@ pub async fn update_flow_status_after_job_completion(
|
||||
false,
|
||||
same_worker_tx.clone(),
|
||||
worker_dir,
|
||||
keep_job_dir,
|
||||
base_internal_url,
|
||||
if stop_early {
|
||||
Some(skip_if_stop_early)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
base_internal_url,
|
||||
)
|
||||
.await?);
|
||||
}
|
||||
@@ -557,7 +559,7 @@ async fn has_failure_module<'c>(
|
||||
flow: Uuid,
|
||||
tx: &mut sqlx::Transaction<'c, sqlx::Postgres>,
|
||||
) -> Result<bool, Error> {
|
||||
sqlx::query_scalar::<_, Option<bool>>(
|
||||
sqlx::query_scalar(
|
||||
"
|
||||
SELECT raw_flow->'failure_module' != 'null'::jsonb
|
||||
FROM queue
|
||||
@@ -568,7 +570,6 @@ async fn has_failure_module<'c>(
|
||||
.fetch_one(tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("error during retrieval of has_failure_module: {e}")))
|
||||
.map(|v| v.unwrap_or(false))
|
||||
}
|
||||
|
||||
fn next_retry(retry: &Retry, status: &RetryStatus) -> Option<(u16, Duration)> {
|
||||
@@ -582,9 +583,9 @@ async fn compute_bool_from_expr(
|
||||
expr: String,
|
||||
flow_args: &Option<serde_json::Value>,
|
||||
result: serde_json::Value,
|
||||
base_internal_url: &str,
|
||||
by_id: Option<IdContext>,
|
||||
creds: Option<EvalCreds>,
|
||||
base_internal_url: &str,
|
||||
) -> error::Result<bool> {
|
||||
let flow_input = flow_args.clone().unwrap_or_else(|| json!({}));
|
||||
match eval_timeout(
|
||||
@@ -597,7 +598,7 @@ async fn compute_bool_from_expr(
|
||||
.into(),
|
||||
creds,
|
||||
by_id,
|
||||
base_internal_url,
|
||||
base_internal_url.to_string(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -721,7 +722,7 @@ async fn transform_input(
|
||||
context,
|
||||
Some(EvalCreds { workspace: workspace.to_string(), token: token.to_string() }),
|
||||
Some(by_id.clone()),
|
||||
base_internal_url,
|
||||
base_internal_url.to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -768,8 +769,8 @@ pub async fn handle_flow(
|
||||
client,
|
||||
last_result,
|
||||
same_worker_tx,
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
worker_dir,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -785,8 +786,8 @@ async fn push_next_flow_job(
|
||||
client: &windmill_api_client::Client,
|
||||
mut last_result: serde_json::Value,
|
||||
same_worker_tx: Sender<Uuid>,
|
||||
worker_dir: &str,
|
||||
base_internal_url: &str,
|
||||
worker_dir: &str,
|
||||
) -> error::Result<()> {
|
||||
let mut i = usize::try_from(status.step)
|
||||
.with_context(|| format!("invalid module index {}", status.step))?;
|
||||
@@ -815,8 +816,9 @@ async fn push_next_flow_job(
|
||||
true,
|
||||
same_worker_tx,
|
||||
worker_dir,
|
||||
None,
|
||||
false,
|
||||
base_internal_url,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -856,7 +858,7 @@ async fn push_next_flow_job(
|
||||
.into(),
|
||||
None,
|
||||
None,
|
||||
base_internal_url,
|
||||
"".to_string(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -1160,8 +1162,8 @@ async fn push_next_flow_job(
|
||||
&status,
|
||||
&status_module,
|
||||
last_result.clone(),
|
||||
previous_id,
|
||||
base_internal_url,
|
||||
previous_id,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
@@ -1502,8 +1504,8 @@ async fn compute_next_flow_transform<'c>(
|
||||
status: &FlowStatus,
|
||||
status_module: &FlowStatusModule,
|
||||
last_result: serde_json::Value,
|
||||
previous_id: String,
|
||||
base_internal_url: &str,
|
||||
previous_id: String,
|
||||
) -> error::Result<(sqlx::Transaction<'c, sqlx::Postgres>, NextFlowTransform)> {
|
||||
match &module.value {
|
||||
FlowModuleValue::Identity => Ok((
|
||||
@@ -1699,12 +1701,12 @@ async fn compute_next_flow_transform<'c>(
|
||||
b.expr.to_string(),
|
||||
&flow_job.args,
|
||||
last_result.clone(),
|
||||
base_internal_url,
|
||||
Some(idcontext.clone()),
|
||||
Some(EvalCreds {
|
||||
workspace: flow_job.workspace_id.clone(),
|
||||
token: token.to_string(),
|
||||
}),
|
||||
base_internal_url,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1918,7 +1920,7 @@ where
|
||||
vars(),
|
||||
Some(EvalCreds { workspace, token }),
|
||||
by_id,
|
||||
base_internal_url,
|
||||
base_internal_url.to_string(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -28,25 +28,12 @@ Flow Steps and Logs will be streamed during execution automatically.
|
||||
The CLI can push specifications to a windmill instance. See the
|
||||
[examples/](./examples/) folder for formats.
|
||||
|
||||
## Switch to a different workspace
|
||||
### Pushing a folder
|
||||
|
||||
```
|
||||
wmill workspace switch <workspace_name>
|
||||
```
|
||||
|
||||
## Sync a workspace
|
||||
|
||||
### Pull
|
||||
|
||||
```
|
||||
wmill sync pull
|
||||
```
|
||||
|
||||
### Push
|
||||
|
||||
```
|
||||
wmill sync push
|
||||
```
|
||||
You can push all files in a folder at once using `wmill push` Files MUST be
|
||||
named resource_name.\<type\>.json. They will be pushed to the remote path they
|
||||
are in, for example the file `u/admin/fib/fib.script.json` will be pushed as a
|
||||
script to u/admin/fib/fib.
|
||||
|
||||
### Pushing individual files
|
||||
|
||||
|
||||
105
cli/apps.ts
105
cli/apps.ts
@@ -1,105 +0,0 @@
|
||||
import { Any, model, property } from "./decoverto.ts";
|
||||
import {
|
||||
AppService,
|
||||
AppWithLastVersion,
|
||||
colors,
|
||||
microdiff,
|
||||
Policy,
|
||||
} from "./deps.ts";
|
||||
import { Difference, PushDiffs, Resource, setValueByPath } from "./types.ts";
|
||||
|
||||
@model()
|
||||
export class AppFile implements Resource, PushDiffs {
|
||||
@property(Any)
|
||||
value: any;
|
||||
@property(() => String)
|
||||
summary: string;
|
||||
@property(Any)
|
||||
policy: Policy;
|
||||
|
||||
|
||||
constructor(value: string, summary: string, policy: Policy) {
|
||||
this.value = value;
|
||||
this.summary = summary;
|
||||
this.policy = policy;
|
||||
}
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (await AppService.existsApp({ workspace, path: remotePath })) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing app...`,
|
||||
),
|
||||
);
|
||||
const changeset: {
|
||||
path?: string | undefined;
|
||||
summary?: string | undefined;
|
||||
value?: any;
|
||||
policy?: Policy | undefined;
|
||||
} = {};
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path[0] !== "value" && diff.path[0] !== "policy" && (
|
||||
diff.path.length !== 1 ||
|
||||
!["path", "summary"].includes(
|
||||
diff.path[0] as string,
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid app diff with path " + diff.path);
|
||||
}
|
||||
if (diff.type === "CREATE" || diff.type === "CHANGE") {
|
||||
setValueByPath(changeset, diff.path, diff.value);
|
||||
} else if (diff.type === "REMOVE") {
|
||||
setValueByPath(changeset, diff.path, null);
|
||||
}
|
||||
}
|
||||
|
||||
const hasChanges = Object.values(changeset).some((v) =>
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
await AppService.updateApp({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow.bold("Creating new app..."));
|
||||
await AppService.createApp({
|
||||
workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
policy: this.policy,
|
||||
summary: this.summary,
|
||||
value: this.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: AppWithLastVersion | undefined;
|
||||
try {
|
||||
existing = await AppService.getAppByPath({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { colors, GlobalUserInfo, setClient, UserService } from "./deps.ts";
|
||||
import { colors, setClient, UserService } from "./deps.ts";
|
||||
import { loginInteractive, tryGetLoginInfo } from "./login.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import {
|
||||
@@ -60,7 +60,7 @@ export async function resolveWorkspace(
|
||||
}
|
||||
}
|
||||
|
||||
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
|
||||
export async function requireLogin(opts: GlobalOptions) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
@@ -71,7 +71,7 @@ export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo>
|
||||
setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
|
||||
|
||||
try {
|
||||
return await UserService.globalWhoami();
|
||||
await UserService.globalWhoami();
|
||||
} catch {
|
||||
console.log(
|
||||
"! Could not reach API given existing credentials. Attempting to reauth...",
|
||||
@@ -88,7 +88,7 @@ export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo>
|
||||
token,
|
||||
workspace.remote.substring(0, workspace.remote.length - 1),
|
||||
);
|
||||
return await UserService.globalWhoami();
|
||||
await UserService.globalWhoami();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// windmill
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.66.0/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.66.0/windmill-api/index.ts";
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.61.1/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.61.1/windmill-api/index.ts";
|
||||
|
||||
// cliffy
|
||||
export { Command } from "https://deno.land/x/cliffy@v0.25.7/command/command.ts";
|
||||
@@ -16,6 +16,7 @@ export {
|
||||
} from "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts";
|
||||
|
||||
// std
|
||||
export { Untar } from "https://deno.land/std@0.176.0/archive/untar.ts";
|
||||
export * as path from "https://deno.land/std@0.176.0/path/mod.ts";
|
||||
export { ensureDir } from "https://deno.land/std@0.176.0/fs/ensure_dir.ts";
|
||||
export {
|
||||
@@ -37,5 +38,3 @@ export {
|
||||
default as microdiff,
|
||||
} from "https://deno.land/x/microdiff@v1.3.1/index.ts";
|
||||
export { default as objectHash } from "https://deno.land/x/object_hash@2.0.3.1/mod.ts";
|
||||
export { default as gitignore_parser } from "npm:gitignore-parser";
|
||||
export { default as JSZip } from "npm:jszip@3.7.1";
|
||||
|
||||
41
cli/flow.ts
41
cli/flow.ts
@@ -10,6 +10,7 @@ import {
|
||||
colors,
|
||||
Command,
|
||||
Flow,
|
||||
FlowModule,
|
||||
FlowService,
|
||||
JobService,
|
||||
microdiff,
|
||||
@@ -18,8 +19,21 @@ import {
|
||||
} from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import { resolve, track_job } from "./script.ts";
|
||||
import { Any, decoverto, model, property } from "./decoverto.ts";
|
||||
import { Any, array, decoverto, model, property } from "./decoverto.ts";
|
||||
|
||||
@model()
|
||||
export class FlowValueFilePart {
|
||||
@property(array(Any))
|
||||
modules: Array<FlowModule>;
|
||||
@property(Any)
|
||||
failure_module?: FlowModule;
|
||||
@property(() => Boolean)
|
||||
same_worker?: boolean;
|
||||
|
||||
constructor(modules: Array<FlowModule>) {
|
||||
this.modules = modules;
|
||||
}
|
||||
}
|
||||
|
||||
// this is effectively "OpenFlow" but a copy as it is accepted by the CLI
|
||||
@model()
|
||||
@@ -28,13 +42,13 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
summary: string;
|
||||
@property(() => String)
|
||||
description?: string;
|
||||
@property(Any)
|
||||
value: any;
|
||||
@property(() => FlowValueFilePart)
|
||||
value: FlowValueFilePart;
|
||||
@property(Any)
|
||||
schema?: any;
|
||||
|
||||
constructor(value: any, summary?: string) {
|
||||
this.summary = summary ?? "";
|
||||
constructor(summary: string, value: FlowValueFilePart) {
|
||||
this.summary = summary;
|
||||
this.value = value;
|
||||
}
|
||||
async pushDiffs(
|
||||
@@ -50,7 +64,7 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing flow... ${remotePath}`,
|
||||
`Applying ${diffs.length} diffs to existing flow...`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -62,7 +76,6 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
description: this.description, // This is OpenAPIed as optional, but isn't
|
||||
schema: this.schema, // Same
|
||||
};
|
||||
const base_changeset = { ...changeset };
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
@@ -87,17 +100,14 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
const update = {
|
||||
...changeset,
|
||||
...base_changeset,
|
||||
}
|
||||
await FlowService.updateFlow({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
requestBody: update,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new flow..."));
|
||||
@@ -121,7 +131,6 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
|
||||
remote = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
@@ -142,7 +151,7 @@ async function push(opts: Options, filePath: string, remotePath: string) {
|
||||
await requireLogin(opts);
|
||||
|
||||
await pushFlow(filePath, workspace.remote, remotePath);
|
||||
console.log(colors.bold.underline.green("Flow pushed"));
|
||||
console.log(colors.bold.underline.green("Flow successfully pushed"));
|
||||
}
|
||||
|
||||
export async function pushFlow(
|
||||
@@ -178,14 +187,16 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary", "edited by"])
|
||||
.header(["path", "summary", "edited at", "edited by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
total.map((x) => [
|
||||
x.path,
|
||||
x.summary,
|
||||
x.edited_at,
|
||||
x.edited_by,
|
||||
x.description ?? "-",
|
||||
]),
|
||||
)
|
||||
.render();
|
||||
|
||||
@@ -95,6 +95,7 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,12 +105,12 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new folder: " + remotePath));
|
||||
console.log(colors.bold.yellow("Creating new folder..."));
|
||||
await FolderService.createFolder({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
name: remotePath,
|
||||
extra_perms: Object.fromEntries(this.extra_perms?.entries() ?? []),
|
||||
extra_perms: this.extra_perms,
|
||||
owners: this.owners,
|
||||
},
|
||||
});
|
||||
@@ -133,7 +134,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushFolder(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Resource pushed"));
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
}
|
||||
|
||||
export async function pushFolder(
|
||||
|
||||
@@ -13,7 +13,7 @@ async function pull(opts: GlobalOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userInfo = await requireLogin(opts);
|
||||
await requireLogin(opts);
|
||||
const list: {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -26,12 +26,6 @@ async function pull(opts: GlobalOptions) {
|
||||
comments: never[];
|
||||
}[] = await fetch(
|
||||
"https://hub.windmill.dev/resource_types/list",
|
||||
{
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"X-email": userInfo.email,
|
||||
},
|
||||
},
|
||||
)
|
||||
.then((r) => r.json())
|
||||
.then((list: { id: number; name: string }[]) =>
|
||||
|
||||
10
cli/login.ts
10
cli/login.ts
@@ -1,6 +1,5 @@
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { colors, getAvailablePort, Secret, Select } from "./deps.ts";
|
||||
import { open } from 'https://deno.land/x/open/index.ts';
|
||||
|
||||
export async function loginInteractive(remote: string) {
|
||||
let token: string | undefined;
|
||||
@@ -48,18 +47,13 @@ export async function browserLogin(
|
||||
}
|
||||
|
||||
const server = Deno.listen({ transport: "tcp", port });
|
||||
const url = `${baseUrl}user/cli?port=${port}`
|
||||
console.log(`Login by going to ${url}`);
|
||||
try {
|
||||
open(url)
|
||||
console.log("Opened browser for you");
|
||||
} catch { }
|
||||
console.log(`Login by going to ${baseUrl}user/cli?port=${port}`);
|
||||
const firstConnection = await server.accept();
|
||||
const httpFirstConnection = Deno.serveHttp(firstConnection);
|
||||
const firstRequest = (await httpFirstConnection.nextRequest())!;
|
||||
const params = new URL(firstRequest.request.url!).searchParams;
|
||||
const token = params.get("token");
|
||||
// const _workspace = params.get("workspace");
|
||||
const _workspace = params.get("workspace");
|
||||
await firstRequest?.respondWith(
|
||||
Response.redirect(baseUrl + "user/cli-success", 302),
|
||||
);
|
||||
|
||||
@@ -13,12 +13,11 @@ import sync from "./sync.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
const VERSION = "v1.69.0";
|
||||
const VERSION = "v1.62.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
.description("A simple CLI tool for windmill.")
|
||||
.action(() => command.showHelp())
|
||||
.globalOption(
|
||||
"--workspace <workspace:string>",
|
||||
"Specify the target workspace. This overrides the default workspace.",
|
||||
|
||||
29
cli/pull.ts
29
cli/pull.ts
@@ -1,34 +1,43 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { colors, Command, JSZip } from "./deps.ts";
|
||||
import { colors, Command, readerFromStreamReader, Untar } from "./deps.ts";
|
||||
import { Workspace } from "./workspace.ts";
|
||||
|
||||
export async function downloadZip(
|
||||
export async function downloadTar(
|
||||
workspace: Workspace,
|
||||
): Promise<JSZip | undefined> {
|
||||
): Promise<Untar | undefined> {
|
||||
const requestHeaders: HeadersInit = new Headers();
|
||||
requestHeaders.set("Authorization", "Bearer " + workspace.token);
|
||||
requestHeaders.set("Content-Type", "application/octet-stream");
|
||||
|
||||
const zipResponse = await fetch(
|
||||
const tarResponse = await fetch(
|
||||
workspace.remote + "api/w/" + workspace.workspaceId +
|
||||
"/workspaces/tarball?archive_type=zip",
|
||||
"/workspaces/tarball",
|
||||
{
|
||||
headers: requestHeaders,
|
||||
method: "GET",
|
||||
},
|
||||
);
|
||||
|
||||
if (!zipResponse.ok) {
|
||||
if (!tarResponse.ok) {
|
||||
console.log(
|
||||
colors.red(
|
||||
"Failed to request tarball from API " + zipResponse.statusText,
|
||||
"Failed to request tarball from API " + tarResponse.statusText,
|
||||
),
|
||||
);
|
||||
throw new Error(await zipResponse.text());
|
||||
console.log(await tarResponse.text());
|
||||
return undefined;
|
||||
}
|
||||
const blob = await zipResponse.blob();
|
||||
return await JSZip.loadAsync(blob);
|
||||
|
||||
const streamReader = tarResponse.body?.getReader();
|
||||
if (!streamReader) {
|
||||
console.log(colors.red("Failed to read tar request body"));
|
||||
return undefined;
|
||||
}
|
||||
console.log(colors.yellow("Streaming tarball to disk..."));
|
||||
const denoReader = readerFromStreamReader(streamReader);
|
||||
const untar = new Untar(denoReader);
|
||||
return untar;
|
||||
}
|
||||
|
||||
async function stub(
|
||||
|
||||
@@ -60,7 +60,7 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
|
||||
) {
|
||||
console.log(
|
||||
"Resource type " + remotePath +
|
||||
" is already taken for the current workspace, but cannot be updated. Is this a conflict with starter?",
|
||||
" is already taken for the current workspace, but cannot be updated. Is this a conflict with starter?",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -91,6 +91,7 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,7 +101,7 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow.bold("Creating new resource type..."));
|
||||
console.log(colors.yellow("Creating new resource type..."));
|
||||
await ResourceService.createResourceType({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
@@ -137,7 +138,7 @@ async function push(opts: PushOptions, filePath: string, name: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushResourceType(workspace.workspaceId, filePath, name);
|
||||
console.log(colors.bold.underline.green("Resource pushed"));
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
|
||||
@@ -80,6 +80,7 @@ export class ResourceFile implements Resource2, PushDiffs {
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ export class ResourceFile implements Resource2, PushDiffs {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(colors.yellow.bold("Creating new resource..."));
|
||||
console.log(colors.yellow("Creating new resource..."));
|
||||
await ResourceService.createResource({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
@@ -155,7 +156,7 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushResource(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green(`Resource ${remotePath} pushed`));
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
|
||||
@@ -76,81 +76,11 @@ async function push(
|
||||
|
||||
await requireLogin(opts);
|
||||
await pushScript(filePath, contentPath, workspace.workspaceId, remotePath);
|
||||
console.log(colors.bold.underline.green(`Script ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
export async function handleScriptMetadata(path: string, workspace: string, alreadySynced: string[]): Promise<boolean> {
|
||||
if (path.endsWith(".script.json")) {
|
||||
const contentPath = await findContentFile(path)
|
||||
return handleFile(contentPath, await Deno.readTextFile(contentPath), workspace, alreadySynced)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleFile(path: string, content: string, workspace: string, alreadySynced: string[]): Promise<boolean> {
|
||||
if (path.endsWith(".ts") || path.endsWith(".py") || path.endsWith(".go") || path.endsWith(".sh")) {
|
||||
if (alreadySynced.includes(path)) {
|
||||
return true
|
||||
}
|
||||
alreadySynced.push(path)
|
||||
const remotePath = path.substring(0, path.length - 3);
|
||||
const metaPath = remotePath + ".script.json";
|
||||
let typed = undefined
|
||||
try {
|
||||
await Deno.stat(metaPath)
|
||||
typed = JSON.parse(await Deno.readTextFile(metaPath))
|
||||
typed = decoverto.type(ScriptFile).plainToInstance(typed);
|
||||
} catch { }
|
||||
const language = inferContentTypeFromFilePath(path);
|
||||
|
||||
try {
|
||||
const remote = await ScriptService.getScriptByPath({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
await ScriptService.createScript({
|
||||
workspace,
|
||||
requestBody: {
|
||||
content,
|
||||
description: typed.description,
|
||||
language,
|
||||
path: remotePath,
|
||||
summary: typed.summary,
|
||||
is_template: typed.is_template,
|
||||
kind: typed.kind,
|
||||
lock: typed.lock,
|
||||
parent_hash: remote.hash,
|
||||
schema: typed.schema,
|
||||
},
|
||||
});
|
||||
console.log(colors.yellow.bold(`Creating script with a parent ${remotePath}`))
|
||||
} catch {
|
||||
// no parent hash
|
||||
await ScriptService.createScript({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
content,
|
||||
description: typed.description,
|
||||
language,
|
||||
path: remotePath,
|
||||
summary: typed.summary,
|
||||
is_template: typed.is_template,
|
||||
kind: typed.kind,
|
||||
lock: typed.lock,
|
||||
parent_hash: undefined,
|
||||
schema: typed.schema,
|
||||
},
|
||||
});
|
||||
console.log(colors.yellow.bold(`Creating script without parent ${remotePath}`))
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
console.log(colors.bold.underline.green("Script successfully pushed"));
|
||||
}
|
||||
|
||||
export async function findContentFile(filePath: string) {
|
||||
console.log("Searching " + filePath);
|
||||
const candidates = [
|
||||
filePath.replace(".script.json", ".ts"),
|
||||
filePath.replace(".script.json", ".py"),
|
||||
@@ -174,7 +104,7 @@ export async function findContentFile(filePath: string) {
|
||||
if (validCandidates.length > 1) {
|
||||
throw new Error(
|
||||
"No content path given and more then one candidate found: " +
|
||||
validCandidates.join(", "),
|
||||
validCandidates.join(", "),
|
||||
);
|
||||
}
|
||||
if (validCandidates.length < 1) {
|
||||
@@ -266,14 +196,16 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary", "language", "created by"])
|
||||
.header(["path", "hash", "kind", "language", "created at", "created by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
total.map((x) => [
|
||||
x.path,
|
||||
x.summary,
|
||||
x.hash,
|
||||
x.kind,
|
||||
x.language,
|
||||
x.created_at,
|
||||
x.created_by,
|
||||
]),
|
||||
)
|
||||
|
||||
1304
cli/sync.ts
1304
cli/sync.ts
File diff suppressed because it is too large
Load Diff
23
cli/types.ts
23
cli/types.ts
@@ -6,7 +6,6 @@ import { ScriptFile } from "./script.ts";
|
||||
import { VariableFile } from "./variable.ts";
|
||||
import { path } from "./deps.ts";
|
||||
import { FolderFile } from "./folder.ts";
|
||||
import { AppFile } from "./apps.ts";
|
||||
|
||||
// TODO: Remove this & replace with a "pull" that lets the object either pull the remote version or return undefined.
|
||||
// Then combine those with diffing, which then gives the new push impl
|
||||
@@ -91,8 +90,7 @@ export function inferTypeFromPath(
|
||||
| FlowFile
|
||||
| ResourceFile
|
||||
| ResourceTypeFile
|
||||
| FolderFile
|
||||
| AppFile {
|
||||
| FolderFile {
|
||||
const typeEnding = getTypeStrFromPath(p);
|
||||
|
||||
if (typeEnding === "folder") {
|
||||
@@ -107,8 +105,6 @@ export function inferTypeFromPath(
|
||||
return decoverto.type(ResourceFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "resource-type") {
|
||||
return decoverto.type(ResourceTypeFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "app") {
|
||||
return decoverto.type(AppFile).plainToInstance(obj);
|
||||
} else {
|
||||
throw new Error("infer type unreachable");
|
||||
}
|
||||
@@ -116,17 +112,12 @@ export function inferTypeFromPath(
|
||||
|
||||
export function getTypeStrFromPath(
|
||||
p: string,
|
||||
):
|
||||
| "script"
|
||||
| "variable"
|
||||
| "flow"
|
||||
| "resource"
|
||||
| "resource-type"
|
||||
| "folder"
|
||||
| "app" {
|
||||
): "script" | "variable" | "flow" | "resource" | "resource-type" | "folder" {
|
||||
const parsed = path.parse(p);
|
||||
if (parsed.ext == ".go" || parsed.ext == ".ts" || parsed.ext == ".sh" || parsed.ext == ".py") {
|
||||
return 'script'
|
||||
if (parsed.ext !== ".json") {
|
||||
throw new Error(
|
||||
"Cannot infer type of non-json file " + JSON.stringify(parsed),
|
||||
);
|
||||
}
|
||||
|
||||
if (parsed.name === "folder.meta") {
|
||||
@@ -137,7 +128,7 @@ export function getTypeStrFromPath(
|
||||
if (
|
||||
typeEnding === "script" || typeEnding === "variable" ||
|
||||
typeEnding === "flow" || typeEnding === "resource" ||
|
||||
typeEnding === "resource-type" || typeEnding === "app"
|
||||
typeEnding === "resource-type"
|
||||
) {
|
||||
return typeEnding;
|
||||
} else {
|
||||
|
||||
@@ -94,21 +94,19 @@ export class VariableFile implements Resource, PushDiffs {
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
await VariableService.updateVariable({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
alreadyEncrypted: true,
|
||||
requestBody: changeset,
|
||||
});
|
||||
|
||||
console.log(changeset);
|
||||
} else {
|
||||
console.log(colors.yellow.bold("Creating new variable..."));
|
||||
console.log(colors.yellow("Creating new variable..."));
|
||||
await VariableService.createVariable({
|
||||
workspace,
|
||||
alreadyEncrypted: true,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
description: this.description,
|
||||
@@ -154,7 +152,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing variable..."));
|
||||
|
||||
await pushVariable(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green(`Variable ${remotePath} pushed`));
|
||||
console.log(colors.bold.underline.green("Variable successfully pushed"));
|
||||
}
|
||||
|
||||
export async function pushVariable(
|
||||
|
||||
@@ -151,11 +151,7 @@ async function switchC(opts: GlobalOptions, workspaceName: string) {
|
||||
|
||||
const all = await allWorkspaces();
|
||||
if (all.findIndex((x) => x.name === workspaceName) === -1) {
|
||||
console.log(colors.red.bold(`! This workspace profile ${workspaceName} does not exist locally.`));
|
||||
console.log("available workspace profiles:")
|
||||
for (const w of all) {
|
||||
console.log(' - ' + w.name)
|
||||
}
|
||||
console.log(colors.red.bold("! This workspace name does not exist."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,8 +184,14 @@ export async function add(
|
||||
workspaceName = await Input.prompt("Name this workspace:");
|
||||
}
|
||||
|
||||
const all = await allWorkspaces();
|
||||
if (all.findIndex((x) => x.name === workspaceName) !== -1) {
|
||||
console.log(colors.red.bold("! Workspace name already exists"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
workspaceId = await Input.prompt({ message: "Enter the ID of this workspace", default: workspaceName, suggestions: [workspaceName] });
|
||||
workspaceId = await Input.prompt("Enter the ID of this workspace");
|
||||
}
|
||||
|
||||
if (!remote) {
|
||||
@@ -201,13 +203,7 @@ export async function add(
|
||||
remote = url.toString();
|
||||
} catch {
|
||||
// not a url
|
||||
remote = new URL(
|
||||
await Input.prompt({
|
||||
message: "Enter the Remote URL",
|
||||
suggestions: ["https://app.windmill.dev/"],
|
||||
default: "https://app.windmill.dev/"
|
||||
}),
|
||||
).toString();
|
||||
remote = new URL(await Input.prompt("Enter the Remote URL")).toString();
|
||||
}
|
||||
}
|
||||
remote = new URL(remote).toString(); // add trailing slash in all cases!
|
||||
@@ -217,24 +213,18 @@ export async function add(
|
||||
token = await loginInteractive(remote);
|
||||
}
|
||||
|
||||
setClient(
|
||||
token,
|
||||
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
|
||||
);
|
||||
let alreadyExists = false
|
||||
try {
|
||||
alreadyExists = await WorkspaceService.existsWorkspace({
|
||||
requestBody: { id: workspaceId },
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(colors.red.bold("! Credentials or instance is invalid. Aborting."));
|
||||
throw e
|
||||
}
|
||||
if (opts.create) {
|
||||
setClient(
|
||||
token,
|
||||
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
|
||||
);
|
||||
|
||||
if (
|
||||
!alreadyExists
|
||||
!await WorkspaceService.existsWorkspace({
|
||||
requestBody: { id: workspaceId },
|
||||
})
|
||||
) {
|
||||
console.log(colors.yellow(`Workspace at id ${workspaceId} on ${remote} does not exist. Creating...`));
|
||||
console.log(colors.yellow("Workspace does not exist. Creating..."));
|
||||
await WorkspaceService.createWorkspace({
|
||||
requestBody: {
|
||||
id: workspaceId,
|
||||
@@ -243,14 +233,6 @@ export async function add(
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (!alreadyExists) {
|
||||
console.log(colors.red.bold(`! Workspace at id ${workspaceId} on ${remote} does not exist. Re-run with --create to create it. Aborting.`));
|
||||
console.log("On that instance and with those credentials, the workspaces that you can access are:")
|
||||
const workspaces = await WorkspaceService.listWorkspaces()
|
||||
for (const workspace of workspaces) {
|
||||
console.log(`- ${workspace.id} (name: ${workspace.name})`)
|
||||
}
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
await addWorkspace({
|
||||
@@ -258,40 +240,28 @@ export async function add(
|
||||
remote: remote,
|
||||
workspaceId: workspaceId,
|
||||
token: token,
|
||||
}, opts);
|
||||
});
|
||||
await Deno.writeTextFile(
|
||||
(await getRootStore()) + "/activeWorkspace",
|
||||
workspaceName,
|
||||
);
|
||||
console.log(colors.green.underline(`Added workspace ${workspaceName} for ${workspaceId} on ${remote}!`));
|
||||
console.log(colors.green.underline("Succesfully added workspace!"));
|
||||
}
|
||||
|
||||
export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
export async function addWorkspace(workspace: Workspace) {
|
||||
workspace.remote = new URL(workspace.remote).toString(); // add trailing slash in all cases!
|
||||
const file = await Deno.open((await getRootStore()) + "remotes.ndjson", {
|
||||
append: true,
|
||||
write: true,
|
||||
read: true,
|
||||
read: false,
|
||||
create: true,
|
||||
});
|
||||
await removeWorkspace(workspace.name, true, opts);
|
||||
await file.write(new TextEncoder().encode(JSON.stringify(workspace) + "\n"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
export async function removeWorkspace(name: string, silent: boolean, opts: any) {
|
||||
export async function removeWorkspace(name: string) {
|
||||
const orgWorkspaces = await allWorkspaces();
|
||||
if (orgWorkspaces.findIndex((x) => x.name === name) === -1) {
|
||||
if (!silent) {
|
||||
console.log(colors.red.bold(`! Workspace profile ${name} does not exist locally`));
|
||||
console.log("available workspace profiles:")
|
||||
await list(opts)
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (silent) {
|
||||
console.log(colors.yellow(`Replacing existing workspace ${name}`))
|
||||
}
|
||||
await Deno.writeTextFile(
|
||||
(await getRootStore()) + "remotes.ndjson",
|
||||
orgWorkspaces
|
||||
@@ -299,13 +269,11 @@ export async function removeWorkspace(name: string, silent: boolean, opts: any)
|
||||
.map((x) => JSON.stringify(x))
|
||||
.join("\n"),
|
||||
);
|
||||
if (!silent) {
|
||||
console.log(colors.green.underline(`Succesfully removed workspace ${name}!`));
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(_opts: GlobalOptions, name: string) {
|
||||
await removeWorkspace(name, false, _opts);
|
||||
await removeWorkspace(name);
|
||||
console.log(colors.green.underline("Succesfully removed workspace!"));
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
|
||||
@@ -14,6 +14,7 @@ BigInt.prototype.toJSON = function () {
|
||||
};
|
||||
|
||||
export { pgSql, pgClient } from './pg.ts'
|
||||
export { mySql, mysqlClient } from './mysql.ts'
|
||||
|
||||
export type Sql = string
|
||||
export type Email = string
|
||||
|
||||
@@ -25,8 +25,9 @@ import { type Resource } from "./mod.ts"
|
||||
export function pgClient(
|
||||
db: Resource<"postgresql">
|
||||
) {
|
||||
const databaseUrl = 'postgresql://' + db.user + ':' + db.password + '@' + db.host + ':' + db.port + '/' + db.dbname + '?sslmode=' + db.sslmode
|
||||
return new Client(databaseUrl)
|
||||
db.database = db.dbname
|
||||
db.hostname = db.host
|
||||
return new Client(db)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
625
frontend/package-lock.json
generated
625
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.69.0",
|
||||
"version": "1.62.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -25,7 +25,6 @@
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@windmill-labs/svelte-grid": "^5.1.3",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
@@ -34,6 +33,7 @@
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"lucide-svelte": "^0.104.0",
|
||||
"openapi-typescript-codegen": "^0.23.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.18",
|
||||
@@ -42,37 +42,35 @@
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte": "^3.53.1",
|
||||
"svelte-awesome": "^3.0.0",
|
||||
"svelte-check": "^3.0.2",
|
||||
"svelte-dnd-action": "^0.9.22",
|
||||
"svelte-grid": "^5.1.1",
|
||||
"svelte-heros": "^2.3.5",
|
||||
"svelte-highlight": "^6.2.1",
|
||||
"svelte-overlay": "^1.4.1",
|
||||
"svelte-popperjs": "^1.3.2",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"svelte-range-slider-pips": "^2.1.1",
|
||||
"svelte-splitpanes": "^0.7.3",
|
||||
"svelte2tsx": "^0.6.1",
|
||||
"svelte2tsx": "^0.5.22",
|
||||
"tailwindcss": "^3.2.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.4",
|
||||
"yootils": "^0.3.1"
|
||||
"vite": "^4.0.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@leeoniya/ufuzzy": "^0.9.1",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.7.6",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chart.js": "^3.9.1",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
"lucide-svelte": "^0.115.0",
|
||||
"monaco-editor": "0.33.0",
|
||||
"monaco-editor-workers": "0.33.0",
|
||||
"monaco-languageclient": "2.1.0",
|
||||
@@ -81,10 +79,9 @@
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"tailwind-merge": "^1.9.1",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55.1"
|
||||
"svelte": "^3.53.1"
|
||||
}
|
||||
}
|
||||
|
||||
11
frontend/src/global.d.ts
vendored
11
frontend/src/global.d.ts
vendored
@@ -9,7 +9,7 @@ declare namespace svelte.JSX {
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@windmill-labs/svelte-grid' {
|
||||
declare module 'svelte-grid' {
|
||||
import type { SvelteComponentTyped } from 'svelte'
|
||||
|
||||
export interface Size {
|
||||
@@ -37,7 +37,7 @@ declare module '@windmill-labs/svelte-grid' {
|
||||
|
||||
export interface Props<T> {
|
||||
fillSpace?: boolean
|
||||
items: FilledItem<T>[]
|
||||
items: FilledItem<T>[],
|
||||
rowHeight: number
|
||||
cols: [number, number][]
|
||||
gap?: [number, number]
|
||||
@@ -57,14 +57,13 @@ declare module '@windmill-labs/svelte-grid' {
|
||||
Props<T>,
|
||||
{
|
||||
pointerup: CustomEvent<{ id: string }>
|
||||
mount: CustomEvent<>
|
||||
},
|
||||
Slots<T>
|
||||
> {}
|
||||
> { }
|
||||
}
|
||||
|
||||
declare module '@windmill-labs/svelte-grid/build/helper/index.mjs' {
|
||||
import { ItemLayout } from '@windmill-labs/svelte-grid'
|
||||
declare module 'svelte-grid/build/helper/index.mjs' {
|
||||
import { ItemLayout } from 'svelte-grid'
|
||||
|
||||
const x: {
|
||||
normalize(items: any[], col: any): unknown[]
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.svelte-select-list {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
.nowrap pre code.hljs {
|
||||
whitespace: normal !important;
|
||||
}
|
||||
|
||||
@@ -27,13 +27,6 @@ export type Schema = {
|
||||
|
||||
export type Meta = { ownerKind: OwnerKind; owner: string; name: string }
|
||||
|
||||
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
|
||||
? Acc[number]
|
||||
: Enumerate<N, [...Acc, Acc['length']]>
|
||||
|
||||
/** An inclusive range of integer numbers */
|
||||
export type IntRange<F extends number, T extends number> = F | Exclude<Enumerate<T>, Enumerate<F>> | T
|
||||
|
||||
export function pathToMeta(path: string): Meta {
|
||||
const splitted = path.split('/')
|
||||
let ownerKind: OwnerKind
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { JobService, Preview, ResourceService } from '$lib/gen'
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { emptySchema, emptyString } from '$lib/utils'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import SimpleEditor from './SimpleEditor.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -59,55 +57,16 @@
|
||||
parseJson()
|
||||
}
|
||||
}
|
||||
|
||||
let loading = false
|
||||
async function testConnection() {
|
||||
loading = true
|
||||
const job = await JobService.runScriptPreview({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
language: 'deno' as Preview.language,
|
||||
content: `
|
||||
import { Client } from 'https://deno.land/x/postgres/mod.ts'
|
||||
export async function main(args: any) {
|
||||
const client = new Client("postgres://" + args.user + ":" + args.password + "@" + args.host + ":" + args.port + "/" + args.dbname + "?sslmode=" + args.sslmode)
|
||||
await client.connect()
|
||||
return 'Connection successful'
|
||||
}
|
||||
`,
|
||||
args: {
|
||||
args
|
||||
}
|
||||
}
|
||||
})
|
||||
await new Promise((r) => setTimeout(r, 3000))
|
||||
loading = false
|
||||
const testResult = await JobService.getCompletedJob({
|
||||
workspace: $workspaceStore!,
|
||||
id: job
|
||||
})
|
||||
if (testResult) {
|
||||
sendUserToast(
|
||||
testResult.success ? testResult.result : testResult.result?.['error']?.['message'],
|
||||
!testResult.success
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !notFound}
|
||||
<div class="w-full flex gap-4 flex-row-reverse">
|
||||
<div class="w-full flex flex-row-reverse">
|
||||
<Toggle
|
||||
on:change={(e) => switchTab(e.detail)}
|
||||
options={{
|
||||
right: 'As JSON'
|
||||
}}
|
||||
/>
|
||||
{#if resource_type == 'postgresql'}
|
||||
<Button size="sm" on:click={testConnection}
|
||||
>{#if loading}<Loader2 class="animate-spin mr-2" />{/if} Test connection</Button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="italic text-gray-500 text-xs mb-4"
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
import ApiConnectForm from './ApiConnectForm.svelte'
|
||||
import SearchItems from './SearchItems.svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import WhitelistIp from './WhitelistIp.svelte'
|
||||
|
||||
export let newPageOAuth = false
|
||||
|
||||
@@ -396,7 +395,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
initialPath=""
|
||||
namePlaceholder="{resource_type}"
|
||||
namePlaceholder="my_{resource_type}"
|
||||
kind="resource"
|
||||
/>
|
||||
|
||||
@@ -417,9 +416,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if resource_type == 'postgresql' || resource_type == 'mysql' || resource_type == 'mongodb'}
|
||||
<WhitelistIp />
|
||||
{/if}
|
||||
|
||||
<h2 class="mt-4">Value</h2>
|
||||
<div class="mt-4">
|
||||
@@ -433,7 +429,7 @@
|
||||
{:else}
|
||||
<Path
|
||||
initialPath=""
|
||||
namePlaceholder="{resource_type}"
|
||||
namePlaceholder="my_{resource_type}"
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
kind="resource"
|
||||
|
||||
@@ -106,8 +106,7 @@
|
||||
<div>
|
||||
<Icon class="text-gray-700" data={faUser} scale={SMALL_ICON_SCALE} /><span class="mx-2">
|
||||
By {job.created_by}
|
||||
{#if job.permissioned_as !== `u/${job.created_by}` && job.permissioned_as != job.created_by}but
|
||||
permissioned as {job.permissioned_as}{/if}
|
||||
{#if job.permissioned_as !== `u/${job.created_by}`}but permissioned as {job.permissioned_as}{/if}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
can_write =
|
||||
$userStore != undefined &&
|
||||
(folder?.owners.includes('u/' + $userStore.username) ||
|
||||
($userStore.is_admin ?? false) || ($userStore.is_super_admin ?? false) ||
|
||||
($userStore.is_admin ?? false) ||
|
||||
$userStore.pgroups.findIndex((x) => folder?.owners.includes(x)) != -1)
|
||||
|
||||
perms = Array.from(
|
||||
@@ -168,8 +168,7 @@
|
||||
path: name,
|
||||
kind: 'folder',
|
||||
requestBody: {
|
||||
owner: owner_name,
|
||||
write: true
|
||||
owner: owner_name
|
||||
}
|
||||
})
|
||||
} else if (role == 'writer') {
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate
|
||||
? `\`${arg?.value?.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
? `\`${arg?.value.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
: arg.value
|
||||
? JSON.stringify(arg?.value, null, 4)
|
||||
: ''
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -70,23 +69,15 @@
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
}
|
||||
meta = { ownerKind: 'user', name: namePlaceholder, owner: '' }
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0]
|
||||
|
||||
let newMeta = { ...meta }
|
||||
while (await pathExists(metaToPath(newMeta), kind)) {
|
||||
disabled = true
|
||||
error = 'finding an available name...'
|
||||
newMeta.name = random_adj() + '_' + namePlaceholder
|
||||
let i = 1
|
||||
while (await pathExists(metaToPath(meta), kind)) {
|
||||
meta.name = `${namePlaceholder}_${i}`
|
||||
i += 1
|
||||
}
|
||||
error = ''
|
||||
disabled = false
|
||||
meta = newMeta
|
||||
path = metaToPath(meta)
|
||||
} else {
|
||||
meta = pathToMeta(path)
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
disabled={!can_write}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="resource"
|
||||
namePlaceholder="my_resource"
|
||||
kind="resource"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
export let topButton = false
|
||||
export let loading = false
|
||||
export let noVariablePicker = false
|
||||
export let viewCliRun = false
|
||||
|
||||
export let args: Record<string, any> = decodeArgs($page.url.searchParams.get('args') ?? undefined)
|
||||
|
||||
@@ -209,21 +208,19 @@
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if viewCliRun}
|
||||
<div class="my-10" />
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
endIcon={{ icon: viewCliOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewCliOptions = !viewCliOptions)}
|
||||
>
|
||||
Run it from the CLI
|
||||
</Button>
|
||||
{#if viewCliOptions}
|
||||
<div transition:slide class="mt-2 px-4 pt-2">
|
||||
<InlineCodeCopy content={cliCommand} />
|
||||
<CliHelpBox />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-10" />
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
endIcon={{ icon: viewCliOptions ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewCliOptions = !viewCliOptions)}
|
||||
>
|
||||
Run it from the CLI
|
||||
</Button>
|
||||
{#if viewCliOptions}
|
||||
<div transition:slide class="mt-2 px-4 pt-2">
|
||||
<InlineCodeCopy content={cliCommand} />
|
||||
<CliHelpBox />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="schedule"
|
||||
namePlaceholder={'my_schedule'}
|
||||
kind="schedule"
|
||||
/>
|
||||
<div class="mb-8" />
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Button, Kbd } from './common'
|
||||
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
|
||||
@@ -83,7 +84,7 @@
|
||||
}
|
||||
})
|
||||
sendUserToast(`New script created at hash ${newHash}`)
|
||||
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
|
||||
await goto(`/scripts/edit/${newHash}?step=2`)
|
||||
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
|
||||
} catch (error) {
|
||||
sendUserToast(`Impossible to save the script: ${error.body}`, true)
|
||||
@@ -204,7 +205,7 @@
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
on:enter={() => changeStep(2)}
|
||||
namePlaceholder="script"
|
||||
namePlaceholder="my_script"
|
||||
kind="script"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
|
||||
@@ -245,7 +246,7 @@
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
|
||||
</Button>
|
||||
<!-- <Button
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
@@ -257,7 +258,7 @@
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
|
||||
</Button> -->
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-16 mb-4">
|
||||
<Button
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { userStore } from '$lib/stores'
|
||||
import { Users } from 'lucide-svelte'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import Popover from './Popover.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let extraPerms: Record<string, boolean> = {}
|
||||
@@ -51,9 +50,10 @@
|
||||
|
||||
{#if kind === 'read' || kind === 'write'}
|
||||
<Badge capitalize color="blue" baseClass="border border-blue-200 flex gap-1 items-center">
|
||||
<Popover notClickable>
|
||||
<Users size={12} />
|
||||
<span slot="text">{kind == 'read' ? 'Shared & read-only' : 'Read & Write'} {reason}</span>
|
||||
</Popover>
|
||||
<Users size={12} />
|
||||
{kind}
|
||||
{#if reason}
|
||||
<Tooltip><span class="normal-case">{reason}</span></Tooltip>
|
||||
{/if}
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { UserService, SettingsService, GlobalUserInfo } from '$lib/gen'
|
||||
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import InviteGlobalUser from '$lib/components/InviteGlobalUser.svelte'
|
||||
import { Badge, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import { Alert, Badge, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import SearchItems from './SearchItems.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
let drawer: Drawer
|
||||
let filter = ''
|
||||
@@ -22,14 +22,6 @@
|
||||
drawer?.toggleDrawer?.()
|
||||
}
|
||||
|
||||
export function closeDrawer() {
|
||||
drawer?.closeDrawer()
|
||||
const index = $page.url.href.lastIndexOf('#')
|
||||
const href = $page.url.href
|
||||
const hashRemoved = index === -1 ? href : href.slice(0, index)
|
||||
goto(hashRemoved)
|
||||
}
|
||||
|
||||
let version: string | undefined
|
||||
let users: GlobalUserInfo[] = []
|
||||
let filteredUsers: GlobalUserInfo[] = []
|
||||
@@ -51,7 +43,7 @@
|
||||
/>
|
||||
|
||||
<Drawer bind:this={drawer} on:open={listUsers} size="900px">
|
||||
<DrawerContent overflow_y={false} title="Superadmin Settings" on:close={closeDrawer}>
|
||||
<DrawerContent overflow_y={false} title="Superadmin Settings" on:close={drawer.closeDrawer}>
|
||||
<div class="flex flex-col h-full">
|
||||
<div>
|
||||
<div class="text-xs pt-1 text-gray-500 ">
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { usersWorkspaceStore } from '$lib/stores'
|
||||
|
||||
import type { TruncatedToken, NewToken } from '$lib/gen'
|
||||
import { UserService, SettingsService } from '$lib/gen'
|
||||
import { displayDate, sendUserToast, copyToClipboard } from '$lib/utils'
|
||||
import { displayDate, sendUserToast, getToday, copyToClipboard, truncate } from '$lib/utils'
|
||||
import { faClipboard, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
@@ -20,8 +21,6 @@
|
||||
|
||||
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
let drawer: Drawer
|
||||
|
||||
@@ -36,13 +35,6 @@
|
||||
drawer?.toggleDrawer()
|
||||
}
|
||||
|
||||
export function closeDrawer() {
|
||||
drawer?.closeDrawer()
|
||||
const index = $page.url.href.lastIndexOf('#')
|
||||
const hashRemoved = $page.url.href.slice(0, index)
|
||||
goto(hashRemoved)
|
||||
}
|
||||
|
||||
async function setPassword(): Promise<void> {
|
||||
if (newPassword) {
|
||||
await UserService.setPassword({
|
||||
@@ -89,7 +81,7 @@
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawer} size="800px">
|
||||
<DrawerContent title="User Settings" on:close={closeDrawer}>
|
||||
<DrawerContent title="User Settings" on:close={drawer.toggleDrawer}>
|
||||
<div class="flex flex-col h-full">
|
||||
<div>
|
||||
<div class="text-xs pt-1 pb-2 text-gray-500 ">
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="variable"
|
||||
namePlaceholder="my_variable"
|
||||
kind="variable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { WorkerService } from '$lib/gen'
|
||||
import { Alert } from './common'
|
||||
|
||||
let ips: string[] | undefined = undefined
|
||||
|
||||
WorkerService.listWorkers({}).then((workers) => {
|
||||
ips = [
|
||||
...new Set(
|
||||
workers
|
||||
.filter((worker) => {
|
||||
const date = new Date().getTime() - 300 * 60
|
||||
const ping_at = new Date(worker.ping_at).getTime()
|
||||
return worker.ip != 'unretrievable IP' && ping_at > date
|
||||
})
|
||||
.map((worker) => worker.ip)
|
||||
)
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if ips}
|
||||
<div class="mt-2" />
|
||||
<Alert size="xs" type="info" title="IPs to whitelist">
|
||||
<span class="text-gray-600">If necessary, the workers IPs to whitelist are:</span>
|
||||
{ips.join(', ')}
|
||||
</Alert>
|
||||
{/if}
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div class="w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm">
|
||||
Results
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../../inputType'
|
||||
import type { InlineScript } from '../../types'
|
||||
import RunnableComponent from './RunnableComponent.svelte'
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { InlineScript } from '../types'
|
||||
import RunnableComponent from './helpers/RunnableComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
@@ -1,15 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { loadIcon } from '../icon'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -20,11 +19,10 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
export let preclickAction: (() => Promise<void>) | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
@@ -32,40 +30,15 @@
|
||||
let runnableComponent: RunnableComponent
|
||||
let disabled: boolean | undefined = undefined
|
||||
let fillContainer: boolean | undefined = undefined
|
||||
let goto: string | undefined = undefined
|
||||
|
||||
let isLoading: boolean = false
|
||||
let ownClick: boolean = false
|
||||
let triggerOnAppLoad = false
|
||||
|
||||
let beforeIcon: undefined | string = undefined
|
||||
let afterIcon: undefined | string = undefined
|
||||
|
||||
let beforeIconComponent: any
|
||||
let afterIconComponent: any
|
||||
|
||||
$: beforeIcon && handleBeforeIcon()
|
||||
$: afterIcon && handleAfterIcon()
|
||||
|
||||
async function handleBeforeIcon() {
|
||||
if (beforeIcon) {
|
||||
beforeIconComponent = await loadIcon(beforeIcon)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAfterIcon() {
|
||||
if (afterIcon) {
|
||||
afterIconComponent = await loadIcon(afterIcon)
|
||||
}
|
||||
}
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<Array<any>>
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: triggerOnAppLoad && runnableComponent?.runComponent()
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
@@ -87,13 +60,8 @@
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.goto} bind:value={goto} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
<InputValue {id} input={configuration.beforeIcon} bind:value={beforeIcon} />
|
||||
<InputValue {id} input={configuration.afterIcon} bind:value={afterIcon} />
|
||||
<InputValue {id} input={configuration.triggerOnAppLoad} bind:value={triggerOnAppLoad} />
|
||||
|
||||
<InputValue
|
||||
row={extraQueryParams['row']}
|
||||
{id}
|
||||
@@ -110,19 +78,13 @@
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
{goto}
|
||||
>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
{#if errorsMessage}
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
<Button
|
||||
btnClasses={twMerge(
|
||||
$app.css?.['buttoncomponent']?.['button']?.class,
|
||||
customCss?.button.class,
|
||||
fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
|
||||
btnClasses={fillContainer ? 'w-full h-full' : ''}
|
||||
{disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
@@ -147,14 +109,8 @@
|
||||
{color}
|
||||
{loading}
|
||||
>
|
||||
<span class="truncate inline-flex gap-2 items-center">
|
||||
{#if beforeIconComponent}
|
||||
<svelte:component this={beforeIconComponent} size={14} />
|
||||
{/if}
|
||||
<div>{labelValue}</div>
|
||||
{#if afterIconComponent}
|
||||
<svelte:component this={afterIconComponent} size={14} />
|
||||
{/if}
|
||||
<span class="truncate">
|
||||
{labelValue}
|
||||
</span>
|
||||
</Button>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as AppButton } from './AppButton.svelte'
|
||||
export { default as AppForm } from './AppForm.svelte'
|
||||
export { default as AppFormButton } from './AppFormButton.svelte'
|
||||
@@ -11,7 +11,6 @@
|
||||
CategoryScale,
|
||||
BarElement
|
||||
} from 'chart.js'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
@@ -46,18 +45,7 @@
|
||||
theme3: ['#e74a3b', '#4e73df', '#1cc88a', '#36b9cc', '#f6c23e']
|
||||
}[theme]
|
||||
|
||||
const lineOptions: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const barOptions: ChartOptions<'bar'> = {
|
||||
const options = {
|
||||
responsive: true,
|
||||
animation: false,
|
||||
maintainAspectRatio: false,
|
||||
@@ -85,9 +73,9 @@
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
{#if lineChart}
|
||||
<Line {data} options={lineOptions} />
|
||||
<Line {data} {options} />
|
||||
{:else}
|
||||
<Bar {data} options={barOptions} />
|
||||
<Bar {data} {options} />
|
||||
{/if}
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
@@ -9,14 +9,12 @@
|
||||
LinearScale,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
BarElement,
|
||||
type Point
|
||||
BarElement
|
||||
} from 'chart.js'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Scatter from 'svelte-chartjs/Scatter.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { ChartOptions, ChartData } from 'chart.js'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -61,11 +59,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
} as ChartOptions<'scatter'>
|
||||
}
|
||||
|
||||
$: data = {
|
||||
datasets: result ?? []
|
||||
} as ChartData<'scatter', (number | Point)[], unknown>
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
@@ -7,9 +7,6 @@
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -17,12 +14,10 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let extraStyle: string | undefined = undefined
|
||||
let result: string | undefined = undefined
|
||||
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
|
||||
let copyButton: boolean
|
||||
@@ -61,6 +56,7 @@
|
||||
$: style && (classes = getClasses())
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.extraStyle} bind:value={extraStyle} />
|
||||
<InputValue {id} input={configuration.style} bind:value={style} />
|
||||
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
|
||||
|
||||
@@ -72,16 +68,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
|
||||
<svelte:element
|
||||
this={component}
|
||||
class={twMerge(
|
||||
'whitespace-pre-wrap',
|
||||
$app.css?.['textcomponent']?.['text']?.class,
|
||||
customCss?.text?.class,
|
||||
classes
|
||||
)}
|
||||
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
|
||||
>
|
||||
<svelte:element this={component} class="whitespace-pre-wrap {classes}" style={extraStyle}>
|
||||
{String(result)}
|
||||
</svelte:element>
|
||||
{#if copyButton && result}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user