Compare commits
2 Commits
v1.63.1
...
rf/fixGrap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fceae4f85 | ||
|
|
c1cfed840a |
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
|
||||
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,48 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add INCLUDE_HEADERS env variable to pass value from request headers ([0921ba0](https://github.com/windmill-labs/windmill/commit/0921ba008535e945f2ec3255728c2e8c1f4c36dc))
|
||||
* add WHITELIST_WORKSPACES and BLACKLIST_WORKSPACES ([99568ea](https://github.com/windmill-labs/windmill/commit/99568eaa473d57123a7dde4007f8812e0053fb3f))
|
||||
* Add workspace webhook ([#1158](https://github.com/windmill-labs/windmill/issues/1158)) ([b9ac60f](https://github.com/windmill-labs/windmill/commit/b9ac60f8bb0662e364606c4b7b8a6e3c1e7e4041))
|
||||
* adding worker_busy ([23007f7](https://github.com/windmill-labs/windmill/commit/23007f7a71630fc2040e1be39db83ba56689e3c4))
|
||||
* **cli:** 2-Way sync ([#1071](https://github.com/windmill-labs/windmill/issues/1071)) ([cdd1619](https://github.com/windmill-labs/windmill/commit/cdd16195aeaf32e1f1d0648f48e4843954d16d9c))
|
||||
* **frontend:** App initial loading animations ([#1176](https://github.com/windmill-labs/windmill/issues/1176)) ([3305481](https://github.com/windmill-labs/windmill/commit/3305481d5d4ce598ceb57256cea851869cdaf25e))
|
||||
* **python:** add ADDITIONAL_PYTHON_PATHS ([14b32be](https://github.com/windmill-labs/windmill/commit/14b32be8b229372c57a167fd74cb958a96f0e8e6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **frontend:** Render popups above components in app editor ([#1171](https://github.com/windmill-labs/windmill/issues/1171)) ([bc8d1a3](https://github.com/windmill-labs/windmill/commit/bc8d1a375ec7886357ce0ef5971bb35013c94d61))
|
||||
* **frontend:** Various fixes and improvements ([#1177](https://github.com/windmill-labs/windmill/issues/1177)) ([9f5500c](https://github.com/windmill-labs/windmill/commit/9f5500c1965ea50796d3bf289c0f9e0c929427f4))
|
||||
* navigate to new script page before saving script ([f171cd8](https://github.com/windmill-labs/windmill/commit/f171cd8b7c46677173572bac256cbb489a1b8526))
|
||||
|
||||
## [1.61.1](https://github.com/windmill-labs/windmill/compare/v1.61.0...v1.61.1) (2023-01-31)
|
||||
|
||||
|
||||
|
||||
14
Caddyfile
14
Caddyfile
@@ -1,15 +1,5 @@
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
http://{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* http://lsp:3001
|
||||
}
|
||||
|
||||
https://{$BASE_URL} {
|
||||
bind {$ADDRESS}
|
||||
reverse_proxy /ws/* http://localhost:3001
|
||||
}
|
||||
reverse_proxy /* http://windmill:8000
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
104
README.md
104
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">
|
||||
@@ -24,13 +24,13 @@ Open-source developer infrastructure for internal tools. Self-hostable alternati
|
||||
|
||||
**Try it (personal workspaces are free forever)**: <https://app.windmill.dev>
|
||||
|
||||
**Documentation**: <https://www.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>
|
||||
**Contributor's guide**: <https://docs.windmill.dev/docs/contributors_guide>
|
||||
|
||||
**Roadmap**: <https://github.com/orgs/windmill-labs/projects/2>
|
||||
|
||||
@@ -43,29 +43,32 @@ mirrors, and support: contact ruben@windmill.dev.
|
||||
|
||||
# Windmill
|
||||
|
||||
Windmill is <b>fully open-sourced (AGPLv3)</b>
|
||||
<p align="center">
|
||||
<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>
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
https://user-images.githubusercontent.com/275584/218350457-bc2fdc3b-e667-4da5-a2bd-3bacc1f0ec79.mp4
|
||||
Windmill is <b>fully open-sourced (AGPLv3)</b>:
|
||||
|
||||
- [Windmill](#windmill)
|
||||
- [Main Concepts](#main-concepts)
|
||||
- [Show me some actual script code](#show-me-some-actual-script-code)
|
||||
- [CLI](#cli)
|
||||
- [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)
|
||||
@@ -96,37 +99,6 @@ 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
|
||||
@@ -163,7 +135,7 @@ scripts from your own github repo. See
|
||||
|
||||
## 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
|
||||
@@ -189,23 +161,33 @@ 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
|
||||
|
||||
We only provide docker-compose setup here. For more advanced setups, like
|
||||
compiling from source or using without a postgres super user, see
|
||||
[documentation](https://docs.windmill.dev/docs/advanced/self_host)
|
||||
[documentation](https://docs.windmill.dev/docs/how-tos/self_host)
|
||||
|
||||
### Docker compose
|
||||
|
||||
`docker compose up` with the following docker-compose is sufficient:
|
||||
<https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml>
|
||||
|
||||
Go to http://localhost et voilà :)
|
||||
Go to https://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
|
||||
|
||||
@@ -213,14 +195,7 @@ From there, you can create other users (do not forget to change the password!)
|
||||
|
||||
### Kubernetes (k8s) and Helm charts
|
||||
|
||||
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.
|
||||
|
||||
We publish helm charts at: <https://github.com/windmill-labs/windmill-helm-charts>
|
||||
|
||||
### Commercial license
|
||||
|
||||
@@ -232,14 +207,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:
|
||||
@@ -256,13 +231,12 @@ the following format:
|
||||
|
||||
and mount it at `/usr/src/app/oauth.json`.
|
||||
|
||||
The redirect url for the oauth clients is:
|
||||
`<instance_url>/user/login_callback/<client>`
|
||||
The redirect url for the oauth clients is: `<instance_url>/user/login_callback/<client>`
|
||||
|
||||
[The list of all possible "connect an app" oauth clients](https://github.com/windmill-labs/windmill/blob/main/backend/oauth_connect.json)
|
||||
|
||||
To add more "connect an app" OAuth clients to the Windmill project, read the
|
||||
[Contributor's guide](https://docs.windmill.dev/docs/misc/contributing). We
|
||||
[Contributor's guide](https://docs.windmill.dev/docs/contributors_guide). We
|
||||
welcome contributions!
|
||||
|
||||
You may also add your own custom OAuth2 IdP and OAuth2 Resource provider:
|
||||
@@ -294,8 +268,9 @@ 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
|
||||
|
||||
@@ -303,8 +278,7 @@ you to have it being synced automatically everyday.
|
||||
| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
|
||||
| DATABASE_URL | | The Postgres database url. | All |
|
||||
| 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 |
|
||||
| NUM_WORKERS | 3 | The number of worker per Worker instance (set to 1 on Eks to have 1 pod = 1 worker) | 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 |
|
||||
| JSON_FMT | false | Output the logs in json format instead of logfmt | All |
|
||||
| BASE_URL | http://localhost:8000 | The base url that is exposed publicly to access your instance | Server |
|
||||
@@ -334,12 +308,12 @@ you to have it being synced automatically everyday.
|
||||
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
|
||||
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
|
||||
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
|
||||
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again | |
|
||||
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again |
|
||||
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
|
||||
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |
|
||||
| WHITELIST_WORKSPACES | None | Whitelist of workspaces this worker takes job from | Worker |
|
||||
| BLACKLIST_WORKSPACES | None | Blacklist of workspaces this worker takes job from | Worker |
|
||||
| NEW_USER_WEBHOOK | None | Webhook to notify of a new user added, signup/invite. Can hook back to windmill to send emails | Server |
|
||||
|
||||
|
||||
|
||||
|
||||
## Run a local dev setup
|
||||
|
||||
|
||||
317
backend/Cargo.lock
generated
317
backend/Cargo.lock
generated
@@ -55,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"
|
||||
@@ -217,9 +217,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.6"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e246206a63c9830e118d12c894f56a82033da1a2361f5544deeee3df85c99d9"
|
||||
checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -386,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",
|
||||
@@ -408,9 +408,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.4.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-lock"
|
||||
@@ -555,9 +555,9 @@ 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.17",
|
||||
@@ -661,9 +661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.90"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
|
||||
checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
@@ -673,9 +673,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.90"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
|
||||
checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
@@ -688,15 +688,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.90"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
|
||||
checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.90"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
|
||||
checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -928,9 +928,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.32"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
|
||||
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -976,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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1233,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",
|
||||
@@ -1308,9 +1308,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -1333,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"
|
||||
@@ -1435,9 +1429,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.24"
|
||||
version = "0.14.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -1544,12 +1538,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.5"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1573,14 +1567,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"hermit-abi 0.2.6",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1609,9 +1603,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.61"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1906,14 +1900,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2174,7 +2168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core 0.9.7",
|
||||
"parking_lot_core 0.9.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2193,15 +2187,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.7"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
||||
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2238,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",
|
||||
@@ -2248,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",
|
||||
@@ -2458,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",
|
||||
]
|
||||
@@ -2468,7 +2462,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#be803fafa129c7b50c652f043ae85cf2db872b80"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"built",
|
||||
@@ -2486,7 +2480,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#be803fafa129c7b50c652f043ae85cf2db872b80"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2500,7 +2494,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#be803fafa129c7b50c652f043ae85cf2db872b80"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"heck",
|
||||
@@ -2522,7 +2516,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#be803fafa129c7b50c652f043ae85cf2db872b80"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#8a09b98645276ef3738d3e904e1c32e5de1d657e"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
@@ -2556,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"
|
||||
@@ -2869,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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2905,7 +2890,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#1fd557d549cd72e7da83608b99e5464273abd4b3"
|
||||
source = "git+https://github.com/RustPython/RustPython#b6e447145800ec7888f035bf266329e4b457e5f9"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -2914,7 +2899,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#1fd557d549cd72e7da83608b99e5464273abd4b3"
|
||||
source = "git+https://github.com/RustPython/RustPython#b6e447145800ec7888f035bf266329e4b457e5f9"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2931,7 +2916,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#1fd557d549cd72e7da83608b99e5464273abd4b3"
|
||||
source = "git+https://github.com/RustPython/RustPython#b6e447145800ec7888f035bf266329e4b457e5f9"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2980,7 +2965,7 @@ version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
||||
dependencies = [
|
||||
"windows-sys 0.42.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2994,7 +2979,7 @@ dependencies = [
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3124,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",
|
||||
]
|
||||
@@ -3155,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",
|
||||
@@ -3292,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",
|
||||
]
|
||||
@@ -3343,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",
|
||||
]
|
||||
@@ -3473,7 +3458,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
"webpki-roots",
|
||||
"whoami",
|
||||
]
|
||||
@@ -3517,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"
|
||||
@@ -3599,9 +3571,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "swc_atoms"
|
||||
version = "0.4.36"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10ad195f903dd49e76fd93bc02c4d5fdb34287f9847f73d26c2097090db7cac3"
|
||||
checksum = "731cf66bd8e11030f056f91f9d8af77f83ec4377ff04d1670778a57d1607402a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
@@ -3613,9 +3585,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_common"
|
||||
version = "0.29.31"
|
||||
version = "0.29.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66c967c1c2f17fac8969831752560289e377712091003bdd1af6f025f2d70dc2"
|
||||
checksum = "a97e491d31418cd33fea58e9f893316fc04b30e2b5d0e750c066e2ba4907ae54"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ast_node",
|
||||
@@ -3640,9 +3612,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.96.5"
|
||||
version = "0.96.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84192270c26fc125494ee8895baabf480a95ef8ef83fefb79a94d9fe90c5eb1b"
|
||||
checksum = "a887102d5595b557261aa4bde35f3d71906fba674d4b79cd5c59b4155b12ee2d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"is-macro",
|
||||
@@ -3657,9 +3629,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.124.9"
|
||||
version = "0.124.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd431de25e6c311ffa7dd4d01907d602e5e32311dc5d9bd73cc0993b15a4c069"
|
||||
checksum = "9e75888eabf1ad8a8968e3befc7cd20c10e4721254d3344285bd5c3a42f58dc1"
|
||||
dependencies = [
|
||||
"either",
|
||||
"enum_kind",
|
||||
@@ -3667,7 +3639,6 @@ dependencies = [
|
||||
"num-bigint",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"stacker",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
@@ -3736,9 +3707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
@@ -3796,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",
|
||||
]
|
||||
|
||||
@@ -3873,9 +3843,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
@@ -3895,7 +3865,7 @@ dependencies = [
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.42.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3922,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",
|
||||
@@ -3969,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",
|
||||
@@ -4038,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",
|
||||
@@ -4202,7 +4172,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d0c9a1c85a9e04ad5937c8df9ff2521b8ea91ea6"
|
||||
source = "git+https://github.com/oxidecomputer/typify#cd1f95b66b0d0fbb33537d66043f26512ee3268b"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
@@ -4211,7 +4181,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d0c9a1c85a9e04ad5937c8df9ff2521b8ea91ea6"
|
||||
source = "git+https://github.com/oxidecomputer/typify#cd1f95b66b0d0fbb33537d66043f26512ee3268b"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
@@ -4229,7 +4199,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d0c9a1c85a9e04ad5937c8df9ff2521b8ea91ea6"
|
||||
source = "git+https://github.com/oxidecomputer/typify#cd1f95b66b0d0fbb33537d66043f26512ee3268b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4254,7 +4224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd"
|
||||
dependencies = [
|
||||
"rand 0.8.5",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4422,9 +4392,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.0"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"serde",
|
||||
@@ -4508,9 +4478,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.84"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -4518,9 +4488,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.84"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -4533,9 +4503,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.34"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
|
||||
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -4545,9 +4515,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.84"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4555,9 +4525,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.84"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4568,9 +4538,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.84"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
@@ -4587,9 +4557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.61"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
|
||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -4668,7 +4638,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4695,7 +4665,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4715,7 +4685,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"magic-crypt",
|
||||
"mime_guess",
|
||||
"prometheus",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"retainer",
|
||||
@@ -4749,7 +4718,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4759,12 +4728,12 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4777,7 +4746,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4796,12 +4765,12 @@ dependencies = [
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4809,7 +4778,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4823,7 +4792,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4835,7 +4804,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4850,7 +4819,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4864,7 +4833,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4880,14 +4849,14 @@ dependencies = [
|
||||
"sqlx",
|
||||
"tracing",
|
||||
"ulid",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
"windmill-audit",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -4908,7 +4877,7 @@ dependencies = [
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid 1.3.0",
|
||||
"uuid 1.2.2",
|
||||
"windmill-api-client",
|
||||
"windmill-audit",
|
||||
"windmill-common",
|
||||
@@ -4935,30 +4904,6 @@ dependencies = [
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.63.1"
|
||||
version = "1.61.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -83,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"
|
||||
@@ -121,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"
|
||||
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE
|
||||
workspace_settings
|
||||
ADD
|
||||
COLUMN webhook text;
|
||||
@@ -1 +0,0 @@
|
||||
-- Add down migration script here
|
||||
@@ -1,3 +0,0 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE queue ADD COLUMN mem_peak INTEGER;
|
||||
ALTER TABLE completed_job ADD COLUMN mem_peak INTEGER;
|
||||
@@ -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
@@ -117,25 +117,17 @@
|
||||
},
|
||||
"query": "SELECT script_path FROM queue WHERE id = $1"
|
||||
},
|
||||
"0715955b7e98cc669a88eca6556cd46b7f9c07fcf32a24f85b69720b54e6e95f": {
|
||||
"062859f1d0e5cfba3115f4241115753b86a4ad239708851c998ff5620ebca5b8": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "mem_peak",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT mem_peak FROM queue WHERE id = $1"
|
||||
"query": "UPDATE queue SET last_ping = now() WHERE id = $1"
|
||||
},
|
||||
"07551a32c49da8c0693dd39c6a63b5b2a596ccc0e52e8918160604a5e133dd32": {
|
||||
"describe": {
|
||||
@@ -417,6 +409,28 @@
|
||||
},
|
||||
"query": "INSERT INTO password(email, verified, password_hash, login_type, super_admin, name, company)\n VALUES ($1, $2, $3, 'password', $4, $5, $6)"
|
||||
},
|
||||
"11eb4dd4a2c9b0b759294dde5e8b505c5a4391aa0d8cb629c665711ee0fc04a0": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "logs",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT substr(logs, $1) as logs FROM queue WHERE workspace_id = $2 AND id = $3"
|
||||
},
|
||||
"122090a0f89e5248a0a0f199ebd24582fdb302883aebd2da187ac0084e767ea3": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -544,11 +558,6 @@
|
||||
"name": "plan",
|
||||
"ordinal": 8,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "webhook",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -560,7 +569,6 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
@@ -1223,18 +1231,6 @@
|
||||
},
|
||||
"query": "INSERT INTO usr\n (workspace_id, email, username, is_admin, operator)\n VALUES ($1, $2, $3, $4, $5)"
|
||||
},
|
||||
"33d69b3915ddfde40323ace65c14e39fa4bbc8b5dd50a34e165765eaea1f4966": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE workspace_settings SET webhook = NULL WHERE workspace_id = $1"
|
||||
},
|
||||
"355dcb2cbebd13f0e3bdd4929b9e431b0e6d72716d1c4f9ab6af6adce5b5e4b3": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1481,74 +1477,6 @@
|
||||
},
|
||||
"query": "UPDATE app SET versions = array_append(versions, $1) WHERE id = $2"
|
||||
},
|
||||
"41659beec0e8d6d95258a728e8e4b2b929045a05bd0cfb2293031c0e4a62f04e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Numeric",
|
||||
"Varchar",
|
||||
"Bool",
|
||||
"Int4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n , mem_peak\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28, $29)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
|
||||
},
|
||||
"42e1b5634a9e51247115fa73f85a97b1467c913d012cac9c45bb6a349082dc71": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1638,21 +1566,6 @@
|
||||
},
|
||||
"query": "SELECT app.id, app.path, app.summary, app.versions, app.policy,\n app.extra_perms, app_version.value, \n app_version.created_at, app_version.created_by from app, app_version \n WHERE app_version.id = $1 AND app.id = app_version.app_id AND app.workspace_id = $2"
|
||||
},
|
||||
"44ded7852b72f50099ff6508a7d13cab73c92ba1d07f2304a3bd3f890fcf3b19": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Jsonb"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING"
|
||||
},
|
||||
"453501fbd61efd26647baf9b6ef702ce0bc2e920914e9f08fe5f2a5f4ab03f02": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -1937,11 +1850,6 @@
|
||||
"name": "plan",
|
||||
"ordinal": 8,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "webhook",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -1953,7 +1861,6 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
@@ -2803,19 +2710,6 @@
|
||||
},
|
||||
"query": "UPDATE script SET archived = true WHERE path = $1 AND workspace_id = $2 RETURNING hash"
|
||||
},
|
||||
"8292b7b2cce5319575bc09ad18f29b63270872b6e5c6df1f0a326370058f13b0": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE workspace_settings SET webhook = $1 WHERE workspace_id = $2"
|
||||
},
|
||||
"82f3c4cd1c1f6aea86d66f675442587684391bc32be9ab55ae20aab549b7bba5": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -3465,27 +3359,6 @@
|
||||
},
|
||||
"query": "UPDATE schedule SET path = $1, script_path = $1 WHERE path = $2 AND workspace_id = $3 AND is_flow IS true RETURNING *"
|
||||
},
|
||||
"913c634de21d46b8841f8a7c25c408da7c572f9e685db6351848cbf6e9253efc": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "canceled",
|
||||
"ordinal": 0,
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Uuid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE queue SET mem_peak = GREATEST($1, mem_peak), last_ping = now() WHERE id = $2 RETURNING canceled"
|
||||
},
|
||||
"924e1d5cbb682db0d6121dd911f38cf8948d0e59ca47428daa4f43fce686ba80": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3542,22 +3415,6 @@
|
||||
},
|
||||
"query": "SELECT value from resource WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')"
|
||||
},
|
||||
"95ebdfaf0510b9cad861568cd25d479759d4ea3c3ff4e136aad13a3521525372": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Jsonb"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO folder\n (workspace_id, name, display_name, owners, extra_perms)\n VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING"
|
||||
},
|
||||
"9681c348429f5e647ab55a4a54933faa64018f8539a143641f10d14ddd3a09c4": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3908,26 +3765,6 @@
|
||||
},
|
||||
"query": "SELECT email FROM usr where username = $1 AND workspace_id = $2"
|
||||
},
|
||||
"a34b79872766941cae2d62c99d80e28b7214dd2fcbb68020a63325bbcb34f417": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "webhook",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT webhook FROM workspace_settings WHERE workspace_id = $1"
|
||||
},
|
||||
"a38059dc3574da498ce986c916b6d385b1f18d5bd659ef13c43fafa9daff6bda": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3963,6 +3800,73 @@
|
||||
},
|
||||
"query": "UPDATE flow SET dependency_job = $1 WHERE path = $2 AND workspace_id = $3"
|
||||
},
|
||||
"a46d97b0c65bbbcc29022c2d010a4853541987a58f0098f9c2e3fd6e40b11a52": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Bool",
|
||||
"Int8",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Varchar",
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"script",
|
||||
"preview",
|
||||
"flow",
|
||||
"dependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
"flowdependencies"
|
||||
]
|
||||
},
|
||||
"name": "job_kind"
|
||||
}
|
||||
},
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Jsonb",
|
||||
"Bool",
|
||||
"Bool",
|
||||
{
|
||||
"Custom": {
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"python3",
|
||||
"deno",
|
||||
"go",
|
||||
"bash"
|
||||
]
|
||||
},
|
||||
"name": "script_lang"
|
||||
}
|
||||
},
|
||||
"Numeric",
|
||||
"Varchar",
|
||||
"Bool"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO completed_job AS cj\n ( workspace_id\n , id\n , parent_job\n , created_by\n , created_at\n , started_at\n , duration_ms\n , success\n , script_hash\n , script_path\n , args\n , result\n , logs\n , raw_code\n , raw_lock\n , canceled\n , canceled_by\n , canceled_reason\n , job_kind\n , schedule_path\n , permissioned_as\n , flow_status\n , raw_flow\n , is_flow_step\n , is_skipped\n , language\n , email\n , visible_to_owner\n )\n VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)\n ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)"
|
||||
},
|
||||
"a6145b0482c9e5da245059a80b1563cad20318fd2dd8aef33f9ca97de1826b8b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -4410,34 +4314,6 @@
|
||||
},
|
||||
"query": "SELECT label, concat(substring(token for 10)) as token_prefix, expiration, created_at, last_used_at FROM token WHERE email = $1\n ORDER BY created_at DESC"
|
||||
},
|
||||
"b69f747eae8b13a0a8d6914a3a7ad322554fcfe62cd28c5f0a475f18dd770d61": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "logs",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "mem_peak",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
null,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT substr(logs, $1) as logs, mem_peak FROM queue WHERE workspace_id = $2 AND id = $3"
|
||||
},
|
||||
"b7dd791cd69748ef51b7520f505c0c8bb1b4014a273476eddfecf1ab658a18b4": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -5157,6 +5033,21 @@
|
||||
},
|
||||
"query": "SELECT count(path) FROM app WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2"
|
||||
},
|
||||
"d444e1c1e12a82e9aee5c2ffc4d1d3841bd41dd71344ab155c9842b45bcf30b6": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"VarcharArray",
|
||||
"Jsonb"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO folder\n (workspace_id, name, owners, extra_perms)\n VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING"
|
||||
},
|
||||
"d47bff7d6b54cd6da8bb330f7321c37af5dcbd76f9acad73b5ba1b8a4afb5091": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -5381,18 +5272,6 @@
|
||||
},
|
||||
"query": "SELECT content FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND archived = false AND (workspace_id = $2 OR workspace_id = 'starter'))"
|
||||
},
|
||||
"dcf9e4087732c830278603a47eb1efe99bea1f2ad8770ed3f7dee349b6ec7a3e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE password SET first_time_user = false WHERE email = $1"
|
||||
},
|
||||
"dd466b6a6b51c1802935fbed6be221f9d945711a2f389d377713e9698a8c2638": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
||||
@@ -70,5 +70,4 @@ cookie.workspace = true
|
||||
sha2.workspace = true
|
||||
urlencoding.workspace = true
|
||||
async-stripe.workspace = true
|
||||
lazy_static.workspace = true
|
||||
prometheus.workspace = true
|
||||
lazy_static.workspace = true
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.63.1
|
||||
version: 1.61.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -883,8 +883,6 @@ paths:
|
||||
type: string
|
||||
customer_id:
|
||||
type: string
|
||||
webhook:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/premium_info:
|
||||
get:
|
||||
@@ -964,33 +962,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/edit_webhook:
|
||||
post:
|
||||
summary: edit webhook
|
||||
operationId: editWebhook
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: WorkspaceWebhook
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
webhook:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/users/list:
|
||||
get:
|
||||
summary: list users
|
||||
@@ -3292,8 +3263,6 @@ paths:
|
||||
type: boolean
|
||||
new_logs:
|
||||
type: string
|
||||
mem_peak:
|
||||
type: integer
|
||||
|
||||
/w/{workspace}/jobs/completed/get/{id}:
|
||||
get:
|
||||
@@ -4792,8 +4761,6 @@ components:
|
||||
type: string
|
||||
visible_to_owner:
|
||||
type: boolean
|
||||
mem_peak:
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- running
|
||||
@@ -4880,8 +4847,6 @@ components:
|
||||
type: string
|
||||
visible_to_owner:
|
||||
type: boolean
|
||||
mem_peak:
|
||||
type: integer
|
||||
required:
|
||||
- id
|
||||
- created_by
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::{
|
||||
jobs::script_path_to_payload,
|
||||
users::{require_owner_of_path, Authed, OptAuthed},
|
||||
variables::build_crypt,
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Json, Path, Query},
|
||||
@@ -311,7 +310,6 @@ async fn get_secret_id(
|
||||
async fn create_app(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(app): Json<CreateApp>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -358,12 +356,7 @@ async fn create_app(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateApp { workspace: w_id, path: app.path.clone() },
|
||||
);
|
||||
|
||||
Ok((StatusCode::CREATED, app.path))
|
||||
}
|
||||
@@ -402,7 +395,6 @@ pub async fn get_hub_app_by_id(
|
||||
async fn delete_app(
|
||||
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();
|
||||
@@ -426,10 +418,6 @@ async fn delete_app(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone().clone(),
|
||||
WebhookMessage::DeleteApp { workspace: w_id, path: path.to_owned() },
|
||||
);
|
||||
|
||||
Ok(format!("app {} deleted", path))
|
||||
}
|
||||
@@ -437,7 +425,6 @@ async fn delete_app(
|
||||
async fn update_app(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(ns): Json<EditApp>,
|
||||
@@ -527,14 +514,6 @@ async fn update_app(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateApp {
|
||||
workspace: w_id,
|
||||
old_path: path.to_owned(),
|
||||
new_path: npath.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("app {} updated (npath: {:?})", path, npath))
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
@@ -182,7 +181,6 @@ async fn check_path_conflict<'c>(
|
||||
async fn create_flow(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(nf): Json<NewFlow>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -223,10 +221,6 @@ async fn create_flow(
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateFlow { workspace: w_id.clone(), path: nf.path.clone() },
|
||||
);
|
||||
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let (dependency_job_uuid, mut tx) = push(
|
||||
@@ -286,7 +280,6 @@ async fn update_flow(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, flow_path)): Path<(String, StripPath)>,
|
||||
Json(nf): Json<NewFlow>,
|
||||
) -> Result<String> {
|
||||
@@ -375,14 +368,6 @@ async fn update_flow(
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateFlow {
|
||||
workspace: w_id.clone(),
|
||||
old_path: flow_path.to_owned(),
|
||||
new_path: nf.path.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
let tx = user_db.begin(&authed).await?;
|
||||
let (dependency_job_uuid, mut tx) = push(
|
||||
@@ -465,7 +450,6 @@ async fn exists_flow_by_path(
|
||||
async fn archive_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();
|
||||
@@ -490,10 +474,6 @@ async fn archive_flow_by_path(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::ArchiveFlow { workspace: w_id, path: path.to_owned() },
|
||||
);
|
||||
|
||||
Ok(format!("Flow {path} archived"))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::Authed,
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
@@ -140,7 +139,6 @@ async fn check_name_conflict<'c>(
|
||||
async fn create_folder(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ng): Json<NewFolder>,
|
||||
) -> Result<String> {
|
||||
@@ -195,12 +193,8 @@ async fn create_folder(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateFolder { workspace: w_id, name: ng.name.clone() },
|
||||
);
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(format!("Created folder {}", ng.name))
|
||||
}
|
||||
|
||||
@@ -251,7 +245,6 @@ pub async fn require_is_owner(
|
||||
async fn update_folder(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
Json(ng): Json<UpdateFolder>,
|
||||
) -> Result<String> {
|
||||
@@ -305,12 +298,8 @@ async fn update_folder(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone().clone(),
|
||||
WebhookMessage::UpdateFolder { workspace: w_id, name: name.to_owned() },
|
||||
);
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(format!("Updated folder {}", name))
|
||||
}
|
||||
|
||||
@@ -427,7 +416,6 @@ async fn get_folder_usage(
|
||||
async fn delete_folder(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
) -> Result<String> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
@@ -452,12 +440,6 @@ async fn delete_folder(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteFolder { workspace: w_id, name: name.clone() },
|
||||
);
|
||||
|
||||
Ok(format!("delete folder at name {}", name))
|
||||
}
|
||||
|
||||
@@ -465,7 +447,6 @@ async fn add_owner(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
Json(Owner { owner }): Json<Owner>,
|
||||
) -> Result<String> {
|
||||
@@ -496,12 +477,6 @@ async fn add_owner(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateFolder { workspace: w_id, name: name.clone() },
|
||||
);
|
||||
|
||||
Ok(format!("Added {} to folder {}", owner, name))
|
||||
}
|
||||
|
||||
@@ -535,7 +510,6 @@ async fn remove_owner(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
Json(Owner { owner }): Json<Owner>,
|
||||
) -> Result<String> {
|
||||
@@ -566,11 +540,5 @@ async fn remove_owner(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateFolder { workspace: w_id, name: name.clone() },
|
||||
);
|
||||
|
||||
Ok(format!("Removed {} to folder {}", owner, name))
|
||||
}
|
||||
|
||||
@@ -258,8 +258,6 @@ pub struct CompletedJob {
|
||||
pub is_skipped: bool,
|
||||
pub email: String,
|
||||
pub visible_to_owner: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mem_peak: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -474,7 +472,7 @@ async fn list_jobs(
|
||||
"running",
|
||||
"script_hash",
|
||||
"script_path",
|
||||
"CASE WHEN pg_column_size(args) > 1000 THEN '\"too large args\"'::jsonb ELSE args END",
|
||||
"args",
|
||||
"null as duration_ms",
|
||||
"null as success",
|
||||
"false as deleted",
|
||||
@@ -489,7 +487,6 @@ async fn list_jobs(
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"suspend",
|
||||
"mem_peak",
|
||||
],
|
||||
);
|
||||
let sqlc = list_completed_jobs_query(
|
||||
@@ -509,7 +506,7 @@ async fn list_jobs(
|
||||
"null as running",
|
||||
"script_hash",
|
||||
"script_path",
|
||||
"CASE WHEN pg_column_size(args) > 1000 THEN '\"too large args\"'::jsonb ELSE args END",
|
||||
"args",
|
||||
"duration_ms",
|
||||
"success",
|
||||
"deleted",
|
||||
@@ -524,7 +521,6 @@ async fn list_jobs(
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"null as suspend",
|
||||
"mem_peak",
|
||||
],
|
||||
);
|
||||
let sql = format!(
|
||||
@@ -1002,7 +998,6 @@ struct UnifiedJob {
|
||||
email: String,
|
||||
visible_to_owner: bool,
|
||||
suspend: Option<i32>,
|
||||
mem_peak: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<UnifiedJob> for Job {
|
||||
@@ -1037,7 +1032,6 @@ impl From<UnifiedJob> for Job {
|
||||
is_skipped: uj.is_skipped,
|
||||
email: uj.email,
|
||||
visible_to_owner: uj.visible_to_owner,
|
||||
mem_peak: uj.mem_peak,
|
||||
}),
|
||||
"QueuedJob" => Job::QueuedJob(QueuedJob {
|
||||
workspace_id: uj.workspace_id,
|
||||
@@ -1070,7 +1064,6 @@ impl From<UnifiedJob> for Job {
|
||||
email: uj.email,
|
||||
visible_to_owner: uj.visible_to_owner,
|
||||
suspend: uj.suspend,
|
||||
mem_peak: uj.mem_peak,
|
||||
}),
|
||||
t => panic!("job type {} not valid", t),
|
||||
}
|
||||
@@ -1589,7 +1582,6 @@ pub struct JobUpdate {
|
||||
pub running: Option<bool>,
|
||||
pub completed: Option<bool>,
|
||||
pub new_logs: Option<String>,
|
||||
pub mem_peak: Option<i32>,
|
||||
}
|
||||
|
||||
async fn get_job_update(
|
||||
@@ -1599,8 +1591,8 @@ async fn get_job_update(
|
||||
) -> error::JsonResult<JobUpdate> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let record = sqlx::query!(
|
||||
"SELECT substr(logs, $1) as logs, mem_peak FROM queue WHERE workspace_id = $2 AND id = $3",
|
||||
let logs = query_scalar!(
|
||||
"SELECT substr(logs, $1) as logs FROM queue WHERE workspace_id = $2 AND id = $3",
|
||||
log_offset,
|
||||
&w_id,
|
||||
&id
|
||||
@@ -1608,13 +1600,12 @@ async fn get_job_update(
|
||||
.fetch_optional(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(record) = record {
|
||||
if let Some(logs) = logs {
|
||||
tx.commit().await?;
|
||||
Ok(Json(JobUpdate {
|
||||
running: if !running { Some(true) } else { None },
|
||||
completed: None,
|
||||
new_logs: record.logs,
|
||||
mem_peak: record.mem_peak,
|
||||
new_logs: logs,
|
||||
}))
|
||||
} else {
|
||||
let logs = query_scalar!(
|
||||
@@ -1632,7 +1623,6 @@ async fn get_job_update(
|
||||
running: Some(false),
|
||||
completed: Some(true),
|
||||
new_logs: logs,
|
||||
mem_peak: record.map(|r| r.mem_peak).flatten(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1750,7 +1740,6 @@ async fn list_completed_jobs(
|
||||
"is_skipped",
|
||||
"email",
|
||||
"visible_to_owner",
|
||||
"mem_peak",
|
||||
],
|
||||
)
|
||||
.sql()?;
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::{
|
||||
oauth2::{build_oauth_clients, SlackVerifier},
|
||||
tracing_init::{MyMakeSpan, MyOnResponse},
|
||||
users::{Authed, OptAuthed},
|
||||
webhook_util::WebhookShared,
|
||||
};
|
||||
|
||||
mod apps;
|
||||
@@ -43,7 +42,6 @@ mod tracing_init;
|
||||
mod users;
|
||||
mod utils;
|
||||
mod variables;
|
||||
mod webhook_util;
|
||||
mod worker_ping;
|
||||
mod workspaces;
|
||||
|
||||
@@ -108,8 +106,7 @@ pub async fn run_server(
|
||||
std::env::var("COOKIE_DOMAIN").ok(),
|
||||
))))
|
||||
.layer(Extension(http_client))
|
||||
.layer(CookieManagerLayer::new())
|
||||
.layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone())));
|
||||
.layer(CookieManagerLayer::new());
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.nest(
|
||||
|
||||
@@ -32,10 +32,9 @@ 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;
|
||||
use windmill_common::utils::{not_found_if_none, now_from_db};
|
||||
|
||||
use crate::users::{truncate_token, Authed, NEW_USER_WEBHOOK};
|
||||
use crate::users::{truncate_token, Authed};
|
||||
use crate::workspaces::invite_user_to_all_auto_invite_worspaces;
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
@@ -658,26 +657,14 @@ async fn connect_slack_callback(
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
sqlx::query_as!(
|
||||
Group,
|
||||
"INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING",
|
||||
w_id,
|
||||
"slack",
|
||||
"The group slack commands act on belhalf of",
|
||||
serde_json::json!({username_to_permissioned_as(&authed.username): true})
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO folder
|
||||
(workspace_id, name, display_name, owners, extra_perms)
|
||||
VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING",
|
||||
(workspace_id, name, owners, extra_perms)
|
||||
VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING",
|
||||
&w_id,
|
||||
"slack_bot",
|
||||
"Slack bot",
|
||||
&["g/slack".to_string()],
|
||||
serde_json::json!({"g/slack": true})
|
||||
&[],
|
||||
serde_json::json!({})
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
@@ -974,7 +961,6 @@ async fn login_callback(
|
||||
Some([("method", &client_name[..])].into()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let demo_exists =
|
||||
sqlx::query_scalar!("SELECT EXISTS(SELECT 1 FROM workspace WHERE id = 'demo')")
|
||||
.fetch_one(&mut tx)
|
||||
@@ -996,16 +982,6 @@ async fn login_callback(
|
||||
}
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
|
||||
let _ = http_client
|
||||
.post(&new_user_webhook)
|
||||
.json(&serde_json::json!({"email" : &email, "event": "oauth_signup"}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
|
||||
}
|
||||
|
||||
Ok("Successfully logged in".to_string())
|
||||
} else {
|
||||
Err(error::Error::BadRequest(format!(
|
||||
@@ -1127,7 +1103,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);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
@@ -264,7 +263,6 @@ async fn check_path_conflict<'c>(
|
||||
async fn create_resource(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(resource): Json<CreateResource>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -295,11 +293,6 @@ async fn create_resource(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateResource { workspace: w_id, path: resource.path.clone() },
|
||||
);
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
format!("resource {} created", resource.path),
|
||||
@@ -309,7 +302,6 @@ async fn create_resource(
|
||||
async fn delete_resource(
|
||||
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();
|
||||
@@ -341,18 +333,12 @@ async fn delete_resource(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteResource { workspace: w_id, path: path.to_owned() },
|
||||
);
|
||||
|
||||
Ok(format!("resource {} deleted", path))
|
||||
}
|
||||
|
||||
async fn update_resource(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(ns): Json<EditResource>,
|
||||
@@ -414,15 +400,6 @@ async fn update_resource(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateResource {
|
||||
workspace: w_id,
|
||||
old_path: path.to_owned(),
|
||||
new_path: npath.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("resource {} updated (npath: {:?})", path, npath))
|
||||
}
|
||||
|
||||
@@ -434,7 +411,6 @@ struct UpdateResource {
|
||||
async fn update_resource_value(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(nv): Json<UpdateResource>,
|
||||
) -> Result<String> {
|
||||
@@ -460,14 +436,6 @@ async fn update_resource_value(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateResource {
|
||||
workspace: w_id,
|
||||
old_path: path.to_owned(),
|
||||
new_path: path.to_owned(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("value of resource {} updated", path))
|
||||
}
|
||||
@@ -545,7 +513,6 @@ async fn exists_resource_type(
|
||||
async fn create_resource_type(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(resource_type): Json<CreateResourceType>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -576,11 +543,6 @@ async fn create_resource_type(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateResourceType { name: resource_type.name.clone() },
|
||||
);
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
format!("resource_type {} created", resource_type.name),
|
||||
@@ -612,7 +574,6 @@ async fn check_rt_path_conflict<'c>(
|
||||
async fn delete_resource_type(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
) -> Result<String> {
|
||||
require_admin(authed.is_admin, &authed.username)?;
|
||||
@@ -637,10 +598,6 @@ async fn delete_resource_type(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteResourceType { name: name.clone() },
|
||||
);
|
||||
|
||||
Ok(format!("resource_type {} deleted", name))
|
||||
}
|
||||
@@ -648,7 +605,6 @@ async fn delete_resource_type(
|
||||
async fn update_resource_type(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, name)): Path<(String, String)>,
|
||||
Json(ns): Json<EditResourceType>,
|
||||
) -> Result<String> {
|
||||
@@ -678,10 +634,6 @@ async fn update_resource_type(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateResourceType { name: name.clone() },
|
||||
);
|
||||
|
||||
Ok(format!("resource_type {} updated", name))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
@@ -185,7 +184,6 @@ fn hash_script(ns: &NewScript) -> i64 {
|
||||
async fn create_script(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ns): Json<NewScript>,
|
||||
@@ -402,14 +400,6 @@ async fn create_script(
|
||||
Some([("hash", hash.to_string().as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateScript {
|
||||
workspace: w_id,
|
||||
path: ns.path.clone(),
|
||||
hash: hash.to_string(),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
audit_log(
|
||||
&mut tx,
|
||||
@@ -427,14 +417,6 @@ async fn create_script(
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateScript {
|
||||
workspace: w_id,
|
||||
path: ns.path.clone(),
|
||||
hash: hash.to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
@@ -618,7 +600,6 @@ async fn get_deployment_status(
|
||||
|
||||
async fn archive_script_by_path(
|
||||
authed: Authed,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
@@ -645,10 +626,6 @@ async fn archive_script_by_path(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() },
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -656,7 +633,6 @@ async fn archive_script_by_path(
|
||||
async fn archive_script_by_hash(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path((w_id, hash)): Path<(String, ScriptHash)>,
|
||||
) -> JsonResult<Script> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
@@ -681,18 +657,12 @@ async fn archive_script_by_hash(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() },
|
||||
);
|
||||
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
async fn delete_script_by_hash(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, hash)): Path<(String, ScriptHash)>,
|
||||
) -> JsonResult<Script> {
|
||||
@@ -721,11 +691,6 @@ async fn delete_script_by_hash(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() },
|
||||
);
|
||||
|
||||
Ok(Json(script))
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ use axum::{
|
||||
};
|
||||
use hyper::{header::LOCATION, StatusCode};
|
||||
use rand::rngs::OsRng;
|
||||
use reqwest::Client;
|
||||
use retainer::Cache;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
@@ -1271,15 +1270,10 @@ async fn delete_user(
|
||||
Ok(format!("email {} deleted", &email_to_delete))
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref NEW_USER_WEBHOOK: Option<String> = std::env::var("NEW_USER_WEBHOOK").ok();
|
||||
}
|
||||
|
||||
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?;
|
||||
@@ -1311,16 +1305,6 @@ async fn create_user(
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
|
||||
let _ = http_client
|
||||
.post(&new_user_webhook)
|
||||
.json(&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "new_user"}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
|
||||
}
|
||||
|
||||
invite_user_to_all_auto_invite_worspaces(&db, &nu.email).await?;
|
||||
|
||||
Ok((StatusCode::CREATED, format!("email {} created", nu.email)))
|
||||
@@ -1571,15 +1555,15 @@ async fn 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
|
||||
@@ -1588,25 +1572,6 @@ 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().0.clone() {
|
||||
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,
|
||||
@@ -1647,7 +1612,7 @@ pub async fn create_session_token<'c>(
|
||||
.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_same_site(cookie::SameSite::Lax);
|
||||
cookie.set_http_only(true);
|
||||
cookie.set_path(COOKIE_PATH);
|
||||
if domain.is_some() {
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
oauth2::{AllClients, _refresh_token},
|
||||
users::{require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
BaseUrl,
|
||||
};
|
||||
/*
|
||||
@@ -225,7 +224,6 @@ async fn check_path_conflict<'c>(
|
||||
async fn create_variable(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(variable): Json<CreateVariable>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
@@ -267,11 +265,6 @@ async fn create_variable(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateVariable { workspace: w_id, path: variable.path.clone() },
|
||||
);
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
format!("variable {} created", variable.path),
|
||||
@@ -281,7 +274,6 @@ async fn create_variable(
|
||||
async fn delete_variable(
|
||||
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();
|
||||
@@ -314,11 +306,6 @@ async fn delete_variable(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::DeleteVariable { workspace: w_id, path: path.to_owned() },
|
||||
);
|
||||
|
||||
Ok(format!("variable {} deleted", path))
|
||||
}
|
||||
|
||||
@@ -333,7 +320,6 @@ struct EditVariable {
|
||||
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)>,
|
||||
Json(ns): Json<EditVariable>,
|
||||
@@ -419,15 +405,6 @@ async fn update_variable(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateVariable {
|
||||
workspace: w_id,
|
||||
old_path: path.to_owned(),
|
||||
new_path: npath.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(format!("variable {} updated (npath: {:?})", path, npath))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::Serialize;
|
||||
use tokio::{select, sync::mpsc, time::interval};
|
||||
|
||||
use crate::db::DB;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
// TODO: these aren't synced, they should be moved into the queue abstraction once/if that happens.
|
||||
static ref WEBHOOK_REQUEST_COUNT: prometheus::Histogram = prometheus::register_histogram!(
|
||||
"webhook_request",
|
||||
"Histogram of webhook requests made"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum WebhookMessage {
|
||||
// See https://serde.rs/enum-representations.html#internally-tagged for how this looks in JSON
|
||||
CreateApp { workspace: String, path: String },
|
||||
DeleteApp { workspace: String, path: String },
|
||||
UpdateApp { workspace: String, old_path: String, new_path: String },
|
||||
CreateFlow { workspace: String, path: String },
|
||||
UpdateFlow { workspace: String, old_path: String, new_path: String },
|
||||
ArchiveFlow { workspace: String, path: String },
|
||||
CreateFolder { workspace: String, name: String },
|
||||
UpdateFolder { workspace: String, name: String },
|
||||
DeleteFolder { workspace: String, name: String },
|
||||
DeleteResource { workspace: String, path: String },
|
||||
CreateResource { workspace: String, path: String },
|
||||
UpdateResource { workspace: String, old_path: String, new_path: String },
|
||||
CreateResourceType { name: String },
|
||||
DeleteResourceType { name: String },
|
||||
UpdateResourceType { name: String },
|
||||
CreateScript { workspace: String, path: String, hash: String },
|
||||
UpdateScript { workspace: String, path: String, hash: String },
|
||||
DeleteScript { workspace: String, hash: String },
|
||||
CreateVariable { workspace: String, path: String },
|
||||
UpdateVariable { workspace: String, old_path: String, new_path: String },
|
||||
DeleteVariable { workspace: String, path: String },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WebhookShared {
|
||||
pub channel: mpsc::UnboundedSender<(String, WebhookMessage)>,
|
||||
}
|
||||
|
||||
impl WebhookShared {
|
||||
pub fn new(mut shutdown_rx: tokio::sync::broadcast::Receiver<()>, db: DB) -> Self {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<(String, WebhookMessage)>();
|
||||
let _process = tokio::spawn(async move {
|
||||
let client = reqwest::Client::builder()
|
||||
// TODO: investigate pool timeouts and such if TCP load is high
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.unwrap();
|
||||
let cache = retainer::Cache::new();
|
||||
let mut cache_purge_interval = interval(Duration::from_secs(30));
|
||||
|
||||
loop {
|
||||
select! {
|
||||
biased;
|
||||
_ = shutdown_rx.recv() => break,
|
||||
r = rx.recv() => match r {
|
||||
Some((workspace_id, message)) => {
|
||||
let url_guard = match cache.get(&workspace_id).await {
|
||||
Some(guard) => {
|
||||
guard
|
||||
},
|
||||
None => {
|
||||
let Ok(webook_opt) =
|
||||
sqlx::query_scalar!(
|
||||
"SELECT webhook FROM workspace_settings WHERE workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(
|
||||
&db,
|
||||
)
|
||||
.await else {
|
||||
tracing::error!("Webhook Message to send - but cannot get workspace settings! Workspace: {workspace_id}");
|
||||
continue;
|
||||
};
|
||||
cache.insert(workspace_id.clone(), webook_opt, Duration::from_secs(30)).await;
|
||||
cache.get(&workspace_id).await.unwrap()
|
||||
}
|
||||
};
|
||||
let webook_opt = url_guard.value();
|
||||
if let Some(url) = webook_opt {
|
||||
let timer = WEBHOOK_REQUEST_COUNT.start_timer();
|
||||
let _ = client.post(url).json(&message).send().await;
|
||||
timer.stop_and_record();
|
||||
drop(url_guard);
|
||||
}
|
||||
},
|
||||
None => break,
|
||||
},
|
||||
_ = futures::future::poll_fn(|cx| cache_purge_interval.poll_tick(cx)) => {
|
||||
tracing::trace!("Purging Webhook Cache");
|
||||
cache.purge(10, 0.50).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self { channel: tx }
|
||||
}
|
||||
|
||||
pub fn send_message(&self, workspace_id: String, message: WebhookMessage) {
|
||||
let _ = self.channel.send((workspace_id.clone(), message));
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
db::{UserDB, DB},
|
||||
folders::Folder,
|
||||
resources::{Resource, ResourceType},
|
||||
users::{Authed, WorkspaceInvite, NEW_USER_WEBHOOK},
|
||||
users::{Authed, WorkspaceInvite},
|
||||
utils::require_super_admin,
|
||||
BaseUrl,
|
||||
};
|
||||
@@ -24,7 +24,6 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use reqwest::Client;
|
||||
use stripe::CustomerId;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
@@ -52,7 +51,6 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/delete_invite", post(delete_invite))
|
||||
.route("/get_settings", get(get_settings))
|
||||
.route("/edit_slack_command", post(edit_slack_command))
|
||||
.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))
|
||||
@@ -92,7 +90,6 @@ pub struct WorkspaceSettings {
|
||||
pub auto_invite_operator: Option<bool>,
|
||||
pub customer_id: Option<String>,
|
||||
pub plan: Option<String>,
|
||||
pub webhook: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -120,11 +117,6 @@ struct EditAutoInvite {
|
||||
operator: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditWebhook {
|
||||
webhook: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateWorkspace {
|
||||
id: String,
|
||||
@@ -527,48 +519,6 @@ async fn edit_auto_invite(
|
||||
))
|
||||
}
|
||||
|
||||
async fn edit_webhook(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Authed { is_admin, username, .. }: Authed,
|
||||
Json(ew): Json<EditWebhook>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
if let Some(webhook) = &ew.webhook {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET webhook = $1 WHERE workspace_id = $2",
|
||||
webhook,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET webhook = NULL WHERE workspace_id = $1",
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
"workspaces.edit_webhook",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([("webhook", &format!("{:?}", ew.webhook)[..])].into()),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit webhook for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
async fn list_workspaces_as_super_admin(
|
||||
authed: Authed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
@@ -926,7 +876,6 @@ 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)> {
|
||||
@@ -948,15 +897,6 @@ async fn invite_user(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
|
||||
let _ = http_client
|
||||
.post(&new_user_webhook)
|
||||
.json(&serde_json::json!({"email" : &nu.email, "event": "new_invite"}))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
|
||||
}
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
format!("user with email {} invited", nu.email),
|
||||
|
||||
@@ -45,7 +45,7 @@ lazy_static::lazy_static! {
|
||||
}
|
||||
|
||||
const MAX_FREE_EXECS: i32 = 1000;
|
||||
const MAX_FREE_CONCURRENT_RUNS: i32 = 15;
|
||||
const MAX_FREE_CONCURRENT_RUNS: i32 = 3;
|
||||
|
||||
pub async fn cancel_job<'c>(
|
||||
username: &str,
|
||||
@@ -82,32 +82,7 @@ pub async fn cancel_job<'c>(
|
||||
Ok((tx, job_option))
|
||||
}
|
||||
|
||||
pub async fn pull(
|
||||
db: &Pool<Postgres>,
|
||||
whitelist_workspaces: Option<Vec<String>>,
|
||||
blacklist_workspaces: Option<Vec<String>>,
|
||||
) -> windmill_common::error::Result<Option<QueuedJob>> {
|
||||
let mut workspaces_filter = String::new();
|
||||
if let Some(whitelist) = whitelist_workspaces {
|
||||
workspaces_filter.push_str(&format!(
|
||||
" AND workspace_id IN ({})",
|
||||
whitelist
|
||||
.into_iter()
|
||||
.map(|x| format!("'{x}'"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
));
|
||||
}
|
||||
if let Some(blacklist) = blacklist_workspaces {
|
||||
workspaces_filter.push_str(&format!(
|
||||
" AND workspace_id NOT IN ({})",
|
||||
blacklist
|
||||
.into_iter()
|
||||
.map(|x| format!("'{x}'"))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
));
|
||||
}
|
||||
pub async fn pull(db: &Pool<Postgres>) -> windmill_common::error::Result<Option<QueuedJob>> {
|
||||
/* Jobs can be started if they:
|
||||
* - haven't been started before,
|
||||
* running = false
|
||||
@@ -115,7 +90,7 @@ pub async fn pull(
|
||||
* suspend_until is non-null
|
||||
* and suspend = 0 when the resume messages are received
|
||||
* or suspend_until <= now() if it has timed out */
|
||||
let job: Option<QueuedJob> = sqlx::query_as::<_, QueuedJob>(&format!(
|
||||
let job: Option<QueuedJob> = sqlx::query_as::<_, QueuedJob>(
|
||||
"UPDATE queue
|
||||
SET running = true
|
||||
, started_at = coalesce(started_at, now())
|
||||
@@ -124,17 +99,17 @@ pub async fn pull(
|
||||
WHERE id = (
|
||||
SELECT id
|
||||
FROM queue
|
||||
WHERE ((running = false
|
||||
WHERE ( running = false
|
||||
AND scheduled_for <= now())
|
||||
OR (suspend_until IS NOT NULL
|
||||
AND ( suspend <= 0
|
||||
OR suspend_until <= now()))) {workspaces_filter}
|
||||
OR suspend_until <= now()))
|
||||
ORDER BY scheduled_for
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING *"
|
||||
))
|
||||
RETURNING *",
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
@@ -611,13 +586,16 @@ pub fn canceled_job_to_result(job: &QueuedJob) -> serde_json::Value {
|
||||
}
|
||||
|
||||
pub async fn get_hub_script(path: String, email: &str) -> error::Result<HubScript> {
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.build()
|
||||
.map_err(to_anyhow)?;
|
||||
get_full_hub_script_by_path(email, StripPath(path), client)
|
||||
.await
|
||||
.map(|e| e)
|
||||
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)]
|
||||
@@ -668,8 +646,6 @@ pub struct QueuedJob {
|
||||
pub visible_to_owner: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub suspend: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mem_peak: Option<i32>,
|
||||
}
|
||||
|
||||
impl QueuedJob {
|
||||
|
||||
@@ -77,12 +77,6 @@ pub async fn add_completed_job(
|
||||
None
|
||||
};
|
||||
|
||||
let mem_peak = sqlx::query_scalar!("SELECT mem_peak FROM queue WHERE id = $1", &queued_job.id)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.flatten();
|
||||
let mut tx = db.begin().await?;
|
||||
let job_id = queued_job.id.clone();
|
||||
sqlx::query!(
|
||||
@@ -115,10 +109,9 @@ pub async fn add_completed_job(
|
||||
, language
|
||||
, email
|
||||
, visible_to_owner
|
||||
, mem_peak
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, COALESCE($26, (EXTRACT('epoch' FROM (now())) - EXTRACT('epoch' FROM (COALESCE($6, now()))))*1000), $7, $8, $9,\
|
||||
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28, $29)
|
||||
$10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $27, $28)
|
||||
ON CONFLICT (id) DO UPDATE SET success = $7, result = $11, logs = concat(cj.logs, $12)",
|
||||
queued_job.workspace_id,
|
||||
queued_job.id,
|
||||
@@ -147,8 +140,7 @@ pub async fn add_completed_job(
|
||||
queued_job.language: ScriptLang,
|
||||
duration: Option<i64>,
|
||||
queued_job.email,
|
||||
queued_job.visible_to_owner,
|
||||
mem_peak
|
||||
queued_job.visible_to_owner
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await
|
||||
|
||||
@@ -26,7 +26,7 @@ use windmill_common::{
|
||||
utils::rd_string,
|
||||
variables,
|
||||
};
|
||||
use windmill_queue::{canceled_job_to_result, get_queued_job, pull, JobKind, QueuedJob, CLOUD_HOSTED};
|
||||
use windmill_queue::{canceled_job_to_result, get_queued_job, pull, JobKind, QueuedJob};
|
||||
|
||||
use serde_json::{json, Value};
|
||||
|
||||
@@ -35,7 +35,8 @@ use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
process::{Child, Command},
|
||||
sync::{
|
||||
mpsc::{self, Sender}, watch,
|
||||
mpsc::{self, Sender},
|
||||
oneshot, watch,
|
||||
},
|
||||
time::{interval, sleep, Instant, MissedTickBehavior},
|
||||
};
|
||||
@@ -316,11 +317,6 @@ lazy_static::lazy_static! {
|
||||
);
|
||||
}
|
||||
|
||||
//only matter if CLOUD_HOSTED
|
||||
const MAX_RESULT_SIZE: usize = 1024 * 1024 * 2; // 2MB
|
||||
|
||||
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub async fn run_worker(
|
||||
db: &Pool<Postgres>,
|
||||
@@ -408,13 +404,6 @@ pub async fn run_worker(
|
||||
)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_busy: prometheus::IntGauge = prometheus::register_int_gauge!(prometheus::Opts::new(
|
||||
"worker_busy",
|
||||
"Is the worker busy executing a job?",
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.unwrap();
|
||||
|
||||
let mut jobs_executed = 0;
|
||||
|
||||
let deno_path = std::env::var("DENO_PATH").unwrap_or_else(|_| "/usr/bin/deno".to_string());
|
||||
@@ -431,22 +420,16 @@ pub async fn run_worker(
|
||||
.ok()
|
||||
.map(|x| format!(";{x}"))
|
||||
.unwrap_or_else(|| String::new());
|
||||
|
||||
|
||||
|
||||
|
||||
let max_log_size = std::env::var("MAX_LOG_SIZE")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<i64>().ok())
|
||||
.unwrap_or(500000);
|
||||
let deno_flags = std::env::var("DENO_FLAGS")
|
||||
.ok()
|
||||
.map(|x| x.split(' ').map(|x| x.to_string()).collect());
|
||||
let pip_local_dependencies = std::env::var("PIP_LOCAL_DEPENDENCIES")
|
||||
.ok()
|
||||
.map(|x| x.split(',').map(|x| x.to_string()).collect());
|
||||
let whitelist_workspaces = std::env::var("WHITELIST_WORKSPACES")
|
||||
.ok()
|
||||
.map(|x| x.split(',').map(|x| x.to_string()).collect());
|
||||
let blacklist_workspaces = std::env::var("BLACKLIST_WORKSPACES")
|
||||
.ok()
|
||||
.map(|x| x.split(',').map(|x| x.to_string()).collect());
|
||||
|
||||
let pip_local_dependencies = if pip_local_dependencies == Some(vec!["".to_string()]) {
|
||||
None
|
||||
@@ -474,6 +457,7 @@ pub async fn run_worker(
|
||||
pip_index_url,
|
||||
pip_extra_index_url,
|
||||
pip_trusted_host,
|
||||
max_log_size,
|
||||
deno_flags,
|
||||
deno_auth_tokens,
|
||||
pip_local_dependencies,
|
||||
@@ -497,14 +481,11 @@ pub async fn run_worker(
|
||||
let (same_worker_tx, mut same_worker_rx) = mpsc::channel::<Uuid>(5);
|
||||
|
||||
loop {
|
||||
worker_busy.set(0);
|
||||
|
||||
uptime_metric.inc_by(
|
||||
((Instant::now() - start_time).as_millis() - uptime_metric.get() as u128)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let do_break = async {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_ENV_CHECK {
|
||||
sqlx::query!(
|
||||
@@ -545,9 +526,7 @@ pub async fn run_worker(
|
||||
.await
|
||||
.map_err(|_| Error::InternalErr("Impossible to fetch same_worker job".to_string())))
|
||||
},
|
||||
(job, timer) = {
|
||||
let timer = worker_pull_duration.start_timer();
|
||||
pull(&db, whitelist_workspaces.clone(), blacklist_workspaces.clone()).map(|x| (x, timer)) } => {
|
||||
(job, timer) = {let timer = worker_pull_duration.start_timer(); pull(&db).map(|x| (x, timer)) } => {
|
||||
drop(timer);
|
||||
(false, job)
|
||||
},
|
||||
@@ -557,7 +536,6 @@ pub async fn run_worker(
|
||||
return true;
|
||||
}
|
||||
|
||||
worker_busy.set(1);
|
||||
match next_job {
|
||||
Ok(Some(job)) => {
|
||||
let label_values = [
|
||||
@@ -793,6 +771,7 @@ struct Envs {
|
||||
pip_trusted_host: Option<String>,
|
||||
deno_auth_tokens: String,
|
||||
deno_flags: Option<Vec<String>>,
|
||||
max_log_size: i64,
|
||||
pip_local_dependencies: Option<Vec<String>>,
|
||||
additional_python_paths: Option<Vec<String>>,
|
||||
}
|
||||
@@ -1188,7 +1167,7 @@ mount {{
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_go_job(
|
||||
WorkerConfig { base_internal_url, disable_nuser, disable_nsjail, base_url, .. }: &WorkerConfig,
|
||||
Envs { nsjail_path, go_path, path_env, home_env, .. }: &Envs,
|
||||
Envs { nsjail_path, go_path, path_env, home_env, max_log_size, .. }: &Envs,
|
||||
logs: &mut String,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -1226,6 +1205,7 @@ async fn handle_go_job(
|
||||
job_dir,
|
||||
db,
|
||||
timeout,
|
||||
*max_log_size,
|
||||
go_path,
|
||||
true,
|
||||
skip_go_mod,
|
||||
@@ -1346,7 +1326,7 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
handle_child(&job.id, db, logs, timeout, build_go, false).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, build_go).await?;
|
||||
|
||||
Command::new(nsjail_path)
|
||||
.current_dir(job_dir)
|
||||
@@ -1372,14 +1352,14 @@ func Run(req Req) (interface{{}}, error){{
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?
|
||||
};
|
||||
handle_child(&job.id, db, logs, timeout, child, !disable_nsjail).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_bash_job(
|
||||
WorkerConfig { base_internal_url, disable_nuser, disable_nsjail, base_url, .. }: &WorkerConfig,
|
||||
Envs { nsjail_path, path_env, home_env, .. }: &Envs,
|
||||
Envs { nsjail_path, path_env, home_env, max_log_size, .. }: &Envs,
|
||||
logs: &mut String,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -1451,7 +1431,7 @@ async fn handle_bash_job(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?
|
||||
};
|
||||
handle_child(&job.id, db, logs, timeout, child, !disable_nsjail).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
//for now bash jobs have an empty result object
|
||||
Ok(serde_json::json!(logs
|
||||
.lines()
|
||||
@@ -1471,7 +1451,7 @@ fn capitalize(s: &str) -> String {
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_deno_job(
|
||||
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
|
||||
Envs { nsjail_path, deno_path, path_env, deno_auth_tokens, deno_flags, .. }: &Envs,
|
||||
Envs { nsjail_path, deno_path, path_env, max_log_size, deno_auth_tokens, deno_flags, .. }: &Envs,
|
||||
logs: &mut String,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
@@ -1615,7 +1595,7 @@ run().catch(async (e) => {{
|
||||
}
|
||||
.instrument(trace_span!("create_deno_jail"))
|
||||
.await?;
|
||||
handle_child(&job.id, db, logs, timeout, child, !disable_nsjail).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
@@ -1644,7 +1624,7 @@ lazy_static! {
|
||||
async fn handle_python_job(
|
||||
WorkerConfig { base_internal_url, base_url, disable_nuser, disable_nsjail, .. }: &WorkerConfig,
|
||||
envs @ Envs {
|
||||
nsjail_path, python_path, path_env, additional_python_paths, ..
|
||||
nsjail_path, python_path, path_env, max_log_size, additional_python_paths, ..
|
||||
}: &Envs,
|
||||
requirements_o: Option<String>,
|
||||
job_dir: &str,
|
||||
@@ -1774,7 +1754,7 @@ async fn handle_python_job(
|
||||
} else {
|
||||
sig.args
|
||||
.into_iter()
|
||||
.map(|x| format!("args[\"{}\"] = kwargs.get(\"{}\")", x.name, x.name))
|
||||
.map(|x| format!("args[\"{}\"] = kwargs[\"{}\"]", x.name, x.name))
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
@@ -1791,13 +1771,12 @@ inner_script = __import__("inner")
|
||||
|
||||
with open("args.json") as f:
|
||||
kwargs = json.load(f, strict=False)
|
||||
for k, v in list(kwargs.items()):
|
||||
if v == '<function call>':
|
||||
del kwargs[k]
|
||||
args = {{}}
|
||||
{spread}
|
||||
{transforms}
|
||||
for k, v in list(args.items()):
|
||||
if v == '<function call>':
|
||||
del args[k]
|
||||
|
||||
try:
|
||||
res = inner_script.main(**args)
|
||||
res_json = json.dumps(res, separators=(',', ':'), default=str).replace('\n', '')
|
||||
@@ -1889,7 +1868,7 @@ mount {{
|
||||
.spawn()?
|
||||
};
|
||||
|
||||
handle_child(&job.id, db, logs, timeout, child, !disable_nsjail).await?;
|
||||
handle_child(&job.id, db, logs, timeout, *max_log_size, child).await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
|
||||
@@ -1905,10 +1884,6 @@ async fn read_result(job_dir: &str) -> error::Result<serde_json::Value> {
|
||||
let mut file = File::open(format!("{job_dir}/result.json")).await?;
|
||||
let mut content = "".to_string();
|
||||
file.read_to_string(&mut content).await?;
|
||||
if *CLOUD_HOSTED && content.len() > MAX_RESULT_SIZE {
|
||||
return Err(Error::ExecutionErr("Result is too large for the cloud app (limit 2MB).
|
||||
If using this script as part of the flow, use the shared folder to pass heavy data between steps.".to_owned()));
|
||||
}
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| Error::ExecutionErr(format!("Error parsing result: {e}")))
|
||||
}
|
||||
@@ -2089,7 +2064,7 @@ async fn generate_deno_lock(
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
timeout: i32,
|
||||
Envs { deno_path, .. }: &Envs,
|
||||
Envs { deno_path, max_log_size, .. }: &Envs,
|
||||
) -> error::Result<String> {
|
||||
let _ = write_file(job_dir, "main.ts", code).await?;
|
||||
|
||||
@@ -2107,7 +2082,7 @@ async fn generate_deno_lock(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
handle_child(job_id, db, logs, timeout, * child).await?;
|
||||
handle_child(job_id, db, logs, timeout, *max_log_size, child).await?;
|
||||
|
||||
let path_lock = format!("{job_dir}/lock.json");
|
||||
let mut file = File::open(path_lock).await?;
|
||||
@@ -2138,6 +2113,7 @@ async fn capture_dependency_job(
|
||||
job_dir,
|
||||
db,
|
||||
timeout,
|
||||
envs.max_log_size,
|
||||
&envs.go_path,
|
||||
false,
|
||||
false,
|
||||
@@ -2160,7 +2136,7 @@ async fn pip_compile(
|
||||
pip_extra_index_url,
|
||||
pip_index_url,
|
||||
pip_trusted_host,
|
||||
|
||||
max_log_size,
|
||||
pip_local_dependencies,
|
||||
..
|
||||
}: &Envs,
|
||||
@@ -2198,7 +2174,7 @@ async fn pip_compile(
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
handle_child(job_id, db, logs, timeout, child, false)
|
||||
handle_child(job_id, db, logs, timeout, *max_log_size, child)
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
|
||||
let path_lock = format!("{job_dir}/requirements.txt");
|
||||
@@ -2220,6 +2196,7 @@ async fn install_go_dependencies(
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
timeout: i32,
|
||||
max_log_size: i64,
|
||||
go_path: &str,
|
||||
preview: bool,
|
||||
skip_go_mod: bool,
|
||||
@@ -2233,7 +2210,7 @@ async fn install_go_dependencies(
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
handle_child(job_id, db, logs, timeout, child, false).await?;
|
||||
handle_child(job_id, db, logs, timeout, max_log_size, child).await?;
|
||||
}
|
||||
let child = Command::new(go_path)
|
||||
.current_dir(job_dir)
|
||||
@@ -2241,7 +2218,7 @@ async fn install_go_dependencies(
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
handle_child(job_id, db, logs, timeout, child, false)
|
||||
handle_child(job_id, db, logs, timeout, max_log_size, child)
|
||||
.await
|
||||
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
|
||||
|
||||
@@ -2311,30 +2288,6 @@ async fn get_reserved_variables(
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_mem_peak(pid: Option<u32>, nsjail: bool) -> i32 {
|
||||
if pid.is_none() {
|
||||
return -1
|
||||
}
|
||||
let pid = if nsjail {
|
||||
// This is a bit hacky, but the process id of the nsjail process is the pid of nsjail + 1.
|
||||
// Ideally, we would get the number from fork() itself. This works in MOST cases.
|
||||
pid.unwrap() + 1
|
||||
} else {
|
||||
pid.unwrap()
|
||||
};
|
||||
|
||||
if let Ok(file) = File::open(format!("/proc/{}/status", pid)).await {
|
||||
let mut lines = BufReader::new(file).lines();
|
||||
while let Some(line) = lines.next_line().await.unwrap_or(None) {
|
||||
if line.starts_with("VmHWM:") {
|
||||
return line.split_whitespace().nth(1).and_then(|s| s.parse::<i32>().ok()).unwrap_or(-1);
|
||||
};
|
||||
}
|
||||
-2
|
||||
} else {
|
||||
-3
|
||||
}
|
||||
}
|
||||
/// - wait until child exits and return with exit status
|
||||
/// - read lines from stdout and stderr and append them to the "queue"."logs"
|
||||
/// quitting early if output exceedes MAX_LOG_SIZE characters (not bytes)
|
||||
@@ -2347,21 +2300,14 @@ async fn handle_child(
|
||||
db: &Pool<Postgres>,
|
||||
logs: &mut String,
|
||||
timeout: i32,
|
||||
max_log_size: i64,
|
||||
mut child: Child,
|
||||
nsjail: bool,
|
||||
) -> error::Result<()> {
|
||||
let timeout = Duration::from_secs(u64::try_from(timeout).expect("invalid timeout"));
|
||||
let update_job_interval = Duration::from_millis(500);
|
||||
let ping_interval = Duration::from_secs(5);
|
||||
let cancel_check_interval = Duration::from_millis(500);
|
||||
let write_logs_delay = Duration::from_millis(500);
|
||||
|
||||
let pid = child.id();
|
||||
if let Some(pid) = pid {
|
||||
//set the highest oom priority
|
||||
let mut file = File::create(format!("/proc/{pid}/oom_score_adj")).await?;
|
||||
let _ = file.write_all(b"1000").await;
|
||||
} else {
|
||||
tracing::info!("could not get child pid");
|
||||
}
|
||||
let (set_too_many_logs, mut too_many_logs) = watch::channel::<bool>(false);
|
||||
|
||||
let output = child_joined_output_stream(&mut child);
|
||||
@@ -2371,20 +2317,17 @@ async fn handle_child(
|
||||
|
||||
/* the cancellation future is polled on by `wait_on_child` while
|
||||
* waiting for the child to exit normally */
|
||||
let update_job = async {
|
||||
let cancel_check = async {
|
||||
let db = db.clone();
|
||||
|
||||
let mut interval = interval(update_job_interval);
|
||||
let mut interval = interval(cancel_check_interval);
|
||||
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||
|
||||
loop {
|
||||
tokio::select!(
|
||||
_ = rx.recv() => break,
|
||||
_ = interval.tick() => {
|
||||
let mem_peak = get_mem_peak(pid, nsjail).await;
|
||||
tracing::info!("{job_id} still running. mem peak: {}kB", mem_peak);
|
||||
let mem_peak = if mem_peak > 0 { Some(mem_peak) } else { None };
|
||||
if sqlx::query_scalar!("UPDATE queue SET mem_peak = GREATEST($1, mem_peak), last_ping = now() WHERE id = $2 RETURNING canceled", mem_peak, job_id)
|
||||
if sqlx::query_scalar!("SELECT canceled FROM queue WHERE id = $1", job_id)
|
||||
.fetch_optional(&db)
|
||||
.await
|
||||
.map(|v| Some(true) == v)
|
||||
@@ -2414,7 +2357,7 @@ async fn handle_child(
|
||||
biased;
|
||||
result = child.wait() => return result.map(Ok),
|
||||
Ok(()) = too_many_logs.changed() => KillReason::TooManyLogs,
|
||||
_ = update_job => KillReason::Cancelled,
|
||||
_ = cancel_check => KillReason::Cancelled,
|
||||
_ = sleep(timeout) => KillReason::Timeout,
|
||||
};
|
||||
tx.send(()).await.expect("rx should never be dropped");
|
||||
@@ -2448,13 +2391,8 @@ async fn handle_child(
|
||||
|
||||
/* a future that reads output from the child and appends to the database */
|
||||
let lines = async move {
|
||||
let max_log_size = if *CLOUD_HOSTED {
|
||||
MAX_RESULT_SIZE
|
||||
} else {
|
||||
usize::MAX
|
||||
};
|
||||
/* log_remaining is zero when output limit was reached */
|
||||
let mut log_remaining = max_log_size.saturating_sub(logs.chars().count());
|
||||
let mut log_remaining = (max_log_size as usize).saturating_sub(logs.chars().count());
|
||||
let mut result = io::Result::Ok(());
|
||||
let mut output = output;
|
||||
/* `do_write` resolves the task, but does not contain the Result.
|
||||
@@ -2485,7 +2423,7 @@ async fn handle_child(
|
||||
tracing::info!(%job_id, "Too many logs lines for job {job_id}");
|
||||
let _ = set_too_many_logs.send(true);
|
||||
joined.push_str(&format!(
|
||||
"Job logs or result reached character limit of {MAX_RESULT_SIZE}; killing job."
|
||||
"Job logs or result reached character limit of {max_log_size}; killing job."
|
||||
));
|
||||
/* stop reading and drop our streams fairly quickly */
|
||||
break;
|
||||
@@ -2542,12 +2480,35 @@ async fn handle_child(
|
||||
}
|
||||
}.instrument(trace_span!("child_lines"));
|
||||
|
||||
/* a stream updating "queue"."last_ping" at an interval */
|
||||
|
||||
let (kill_tx, mut kill_rx) = oneshot::channel::<()>();
|
||||
|
||||
let mut interval = interval(ping_interval);
|
||||
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||
|
||||
let db1 = db.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
if let Err(err) = sqlx::query!("UPDATE queue SET last_ping = now() WHERE id = $1", job_id)
|
||||
.execute(&db1)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%job_id, %err, "error setting last ping for job {job_id}: {err}");
|
||||
};
|
||||
},
|
||||
_ = (&mut kill_rx) => return,
|
||||
}
|
||||
}
|
||||
});
|
||||
let (wait_result, _) = tokio::join!(wait_on_child, lines);
|
||||
kill_tx.send(()).expect("send should always work");
|
||||
|
||||
match wait_result {
|
||||
_ if *too_many_logs.borrow() => Err(Error::ExecutionErr(format!(
|
||||
"logs or result reached limit. (current max size: {MAX_RESULT_SIZE} characters)"
|
||||
"logs or result reached limit. Set MAX_LOG_SIZE higher (current: {max_log_size})"
|
||||
))),
|
||||
Ok(Ok(status)) => {
|
||||
if status.success() {
|
||||
@@ -2760,7 +2721,7 @@ async fn handle_python_reqs(
|
||||
pip_extra_index_url,
|
||||
pip_trusted_host,
|
||||
nsjail_path,
|
||||
|
||||
max_log_size,
|
||||
..
|
||||
}: &Envs,
|
||||
job: &QueuedJob,
|
||||
@@ -2855,7 +2816,7 @@ async fn handle_python_reqs(
|
||||
.spawn()?
|
||||
};
|
||||
|
||||
let child = handle_child(&job.id, db, logs, timeout, child, false).await;
|
||||
let child = handle_child(&job.id, db, logs, timeout, *max_log_size, child).await;
|
||||
tracing::info!(
|
||||
worker_name = %worker_name,
|
||||
job_id = %job.id,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { colors, GlobalUserInfo, setClient, UserService } from "./deps.ts";
|
||||
import { loginInteractive, tryGetLoginInfo } from "./login.ts";
|
||||
import { colors, setClient } from "./deps.ts";
|
||||
import { tryGetLoginInfo } from "./login.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import {
|
||||
addWorkspace,
|
||||
getActiveWorkspace,
|
||||
getWorkspaceByName,
|
||||
removeWorkspace,
|
||||
Workspace,
|
||||
} from "./workspace.ts";
|
||||
|
||||
@@ -60,7 +58,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);
|
||||
@@ -69,27 +67,6 @@ export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo>
|
||||
}
|
||||
|
||||
setClient(token, workspace.remote.substring(0, workspace.remote.length - 1));
|
||||
|
||||
try {
|
||||
return await UserService.globalWhoami();
|
||||
} catch {
|
||||
console.log(
|
||||
"! Could not reach API given existing credentials. Attempting to reauth...",
|
||||
);
|
||||
const newToken = await loginInteractive(workspace.remote);
|
||||
if (!newToken) {
|
||||
throw new Error("Could not reauth");
|
||||
}
|
||||
removeWorkspace(workspace.name);
|
||||
workspace.token = newToken;
|
||||
addWorkspace(workspace);
|
||||
|
||||
setClient(
|
||||
token,
|
||||
workspace.remote.substring(0, workspace.remote.length - 1),
|
||||
);
|
||||
return await UserService.globalWhoami();
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryResolveVersion(
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// globally shared decoverto instance
|
||||
import { Decoverto } from "npm:decoverto";
|
||||
const decoverto = new Decoverto();
|
||||
|
||||
// TODO: Properly type FlowModule
|
||||
|
||||
export { Any, array, map, MapShape, model, property } from "npm:decoverto";
|
||||
export { decoverto };
|
||||
38
cli/deps.ts
38
cli/deps.ts
@@ -1,40 +1,32 @@
|
||||
// windmill
|
||||
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";
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.56.0/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.56.0/windmill-api/index.ts";
|
||||
|
||||
// cliffy
|
||||
export { Command } from "https://deno.land/x/cliffy@v0.25.7/command/command.ts";
|
||||
export { Table } from "https://deno.land/x/cliffy@v0.25.7/table/table.ts";
|
||||
export { colors } from "https://deno.land/x/cliffy@v0.25.7/ansi/colors.ts";
|
||||
export { Secret } from "https://deno.land/x/cliffy@v0.25.7/prompt/secret.ts";
|
||||
export { Select } from "https://deno.land/x/cliffy@v0.25.7/prompt/select.ts";
|
||||
export { Confirm } from "https://deno.land/x/cliffy@v0.25.7/prompt/confirm.ts";
|
||||
export { Input } from "https://deno.land/x/cliffy@v0.25.7/prompt/input.ts";
|
||||
export { Command } from "https://deno.land/x/cliffy@v0.25.6/command/command.ts";
|
||||
export { Table } from "https://deno.land/x/cliffy@v0.25.6/table/table.ts";
|
||||
export { colors } from "https://deno.land/x/cliffy@v0.25.6/ansi/colors.ts";
|
||||
export { Secret } from "https://deno.land/x/cliffy@v0.25.6/prompt/secret.ts";
|
||||
export { Select } from "https://deno.land/x/cliffy@v0.25.6/prompt/select.ts";
|
||||
export { Confirm } from "https://deno.land/x/cliffy@v0.25.6/prompt/confirm.ts";
|
||||
export { Input } from "https://deno.land/x/cliffy@v0.25.6/prompt/input.ts";
|
||||
export {
|
||||
DenoLandProvider,
|
||||
UpgradeCommand,
|
||||
} from "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts";
|
||||
} from "https://deno.land/x/cliffy@v0.25.6/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 { Untar } from "https://deno.land/std@0.170.0/archive/untar.ts";
|
||||
export * as path from "https://deno.land/std@0.170.0/path/mod.ts";
|
||||
export { ensureDir } from "https://deno.land/std@0.170.0/fs/ensure_dir.ts";
|
||||
export {
|
||||
copy,
|
||||
readAll,
|
||||
readerFromStreamReader,
|
||||
} from "https://deno.land/std@0.176.0/streams/mod.ts";
|
||||
export { DelimiterStream } from "https://deno.land/std@0.176.0/streams/mod.ts";
|
||||
export { iterateReader } from "https://deno.land/std@0.176.0/streams/iterate_reader.ts";
|
||||
} from "https://deno.land/std@0.170.0/streams/mod.ts";
|
||||
export { DelimiterStream } from "https://deno.land/std@0.170.0/streams/mod.ts";
|
||||
|
||||
// other
|
||||
export { getAvailablePort } from "https://deno.land/x/port@1.0.0/mod.ts";
|
||||
export { default as dir } from "https://deno.land/x/dir@1.5.1/mod.ts";
|
||||
export { passwordGenerator } from "https://deno.land/x/password_generator@latest/mod.ts"; // TODO: I think the version is called latest, but it's still pinned.
|
||||
export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/mod.ts";
|
||||
export * as cbor from "https://deno.land/x/cbor@v1.4.1/index.js";
|
||||
export { default as Murmurhash3 } from "https://deno.land/x/murmurhash@v1.0.0/mod.ts";
|
||||
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";
|
||||
|
||||
169
cli/flow.ts
169
cli/flow.ts
@@ -1,145 +1,16 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import {
|
||||
Difference,
|
||||
GlobalOptions,
|
||||
PushDiffs,
|
||||
Resource,
|
||||
setValueByPath,
|
||||
} from "./types.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
Flow,
|
||||
FlowModule,
|
||||
FlowService,
|
||||
JobService,
|
||||
microdiff,
|
||||
OpenFlowWPath,
|
||||
OpenFlow,
|
||||
Table,
|
||||
} from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import { resolve, track_job } from "./script.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()
|
||||
export class FlowFile implements Resource, PushDiffs {
|
||||
@property(() => String)
|
||||
summary: string;
|
||||
@property(() => String)
|
||||
description?: string;
|
||||
@property(() => FlowValueFilePart)
|
||||
value: FlowValueFilePart;
|
||||
@property(Any)
|
||||
schema?: any;
|
||||
|
||||
constructor(summary: string, value: FlowValueFilePart) {
|
||||
this.summary = summary;
|
||||
this.value = value;
|
||||
}
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (
|
||||
await FlowService.existsFlowByPath({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
})
|
||||
) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing flow...`,
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: Make these optional in backend (not path ofc)
|
||||
const changeset: OpenFlowWPath = {
|
||||
path: remotePath,
|
||||
summary: this.summary,
|
||||
value: this.value,
|
||||
description: this.description, // This is OpenAPIed as optional, but isn't
|
||||
schema: this.schema, // Same
|
||||
};
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path[0] !== "value" && (
|
||||
diff.path.length !== 1 ||
|
||||
!["summary", "description", "schema"].includes(
|
||||
diff.path[0] as string,
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid flow 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) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
await FlowService.updateFlow({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new flow..."));
|
||||
await FlowService.createFlow({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
summary: this.summary,
|
||||
value: this.value,
|
||||
schema: this.schema,
|
||||
description: this.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let remote: Flow | undefined;
|
||||
try {
|
||||
remote = await FlowService.getFlowByPath({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
remote = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff(remote ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type Options = GlobalOptions;
|
||||
|
||||
@@ -159,10 +30,38 @@ export async function pushFlow(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
) {
|
||||
const data = decoverto.type(FlowFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
await data.push(workspace, remotePath);
|
||||
const data: OpenFlow = JSON.parse(await Deno.readTextFile(filePath));
|
||||
if (
|
||||
await FlowService.existsFlowByPath({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
})
|
||||
) {
|
||||
console.log(colors.bold.yellow("Updating existing flow..."));
|
||||
await FlowService.updateFlow({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
summary: data.summary,
|
||||
value: data.value,
|
||||
schema: data.schema,
|
||||
description: data.description,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new flow..."));
|
||||
await FlowService.createFlow({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
summary: data.summary,
|
||||
value: data.value,
|
||||
schema: data.schema,
|
||||
description: data.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions & { showArchived?: boolean }) {
|
||||
|
||||
174
cli/folder.ts
174
cli/folder.ts
@@ -1,122 +1,6 @@
|
||||
import { colors, Command, Folder, FolderService, microdiff } from "./deps.ts";
|
||||
import { colors, Command, Folder, FolderService } from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
Difference,
|
||||
GlobalOptions,
|
||||
PushDiffs,
|
||||
Resource,
|
||||
setValueByPath,
|
||||
} from "./types.ts";
|
||||
import {
|
||||
array,
|
||||
decoverto,
|
||||
map,
|
||||
MapShape,
|
||||
model,
|
||||
property,
|
||||
} from "./decoverto.ts";
|
||||
|
||||
@model()
|
||||
export class FolderFile implements Resource, PushDiffs {
|
||||
@property(array(() => String))
|
||||
owners: Array<string> | undefined;
|
||||
@property(map(() => String, () => Boolean, { shape: MapShape.Object }))
|
||||
extra_perms: Map<string, boolean> | undefined;
|
||||
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
if (remotePath.startsWith("/")) {
|
||||
remotePath = remotePath.substring(1);
|
||||
}
|
||||
if (remotePath.startsWith("f/")) {
|
||||
remotePath = remotePath.substring(2);
|
||||
}
|
||||
|
||||
let existing: Folder | undefined;
|
||||
try {
|
||||
existing = await FolderService.getFolder({ workspace, name: remotePath });
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (remotePath.startsWith("/")) {
|
||||
remotePath = remotePath.substring(1);
|
||||
}
|
||||
if (remotePath.startsWith("f/")) {
|
||||
remotePath = remotePath.substring(2);
|
||||
}
|
||||
|
||||
// TODO: Support this in backend
|
||||
let exists: boolean;
|
||||
try {
|
||||
exists = !!await FolderService.getFolder({ workspace, name: remotePath });
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
if (exists) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing folder...`,
|
||||
),
|
||||
);
|
||||
|
||||
const changeset: {
|
||||
owners?: string[] | undefined;
|
||||
extra_perms?: any;
|
||||
} = {};
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path.length !== 1 ||
|
||||
!["owners", "extra_perms"].includes(diff.path[0] as string)
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid folder 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) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
await FolderService.updateFolder({
|
||||
workspace: workspace,
|
||||
name: remotePath,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new folder..."));
|
||||
await FolderService.createFolder({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
name: remotePath,
|
||||
extra_perms: this.extra_perms,
|
||||
owners: this.owners,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
@@ -137,15 +21,61 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
}
|
||||
|
||||
type FolderFile = {
|
||||
owners: Array<string> | undefined;
|
||||
extra_perms: Record<string, boolean> | undefined;
|
||||
};
|
||||
|
||||
export async function pushFolder(
|
||||
workspace: string,
|
||||
filePath: string,
|
||||
remotePath: string,
|
||||
) {
|
||||
const data = decoverto.type(FolderFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
data.push(workspace, remotePath);
|
||||
if (remotePath.startsWith("/")) {
|
||||
remotePath = remotePath.substring(1);
|
||||
}
|
||||
if (remotePath.startsWith("f/")) {
|
||||
remotePath = remotePath.substring(2);
|
||||
}
|
||||
const data: FolderFile = JSON.parse(await Deno.readTextFile(filePath));
|
||||
let optFolder: Folder | undefined;
|
||||
try {
|
||||
optFolder = await FolderService.getFolder({ workspace, name: remotePath });
|
||||
} catch {
|
||||
optFolder = undefined;
|
||||
}
|
||||
|
||||
if (optFolder) {
|
||||
// for (const [k, v] of Object.entries(optFolder.extra_perms)) {
|
||||
// if (!data.extra_perms || data.extra_perms[k] !== v) {
|
||||
// console.log(colors.red.underline.bold(`Extra Perms missmatch on ${k}`));
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log(colors.yellow("Updating existing folder..."));
|
||||
await FolderService.updateFolder({
|
||||
workspace,
|
||||
name: remotePath,
|
||||
requestBody: {
|
||||
extra_perms: data.extra_perms,
|
||||
owners: data.owners,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow("Creating new folder..."));
|
||||
await FolderService.createFolder({
|
||||
workspace,
|
||||
requestBody: {
|
||||
name: remotePath,
|
||||
extra_perms: data.extra_perms,
|
||||
owners: data.owners,
|
||||
},
|
||||
});
|
||||
|
||||
// HACK: Workaround backend automatically adding current user to folder.
|
||||
await pushFolder(workspace, filePath, remotePath);
|
||||
}
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
|
||||
22
cli/hub.ts
22
cli/hub.ts
@@ -1,6 +1,6 @@
|
||||
import { Command } from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { ResourceTypeFile } from "./resource-type.ts";
|
||||
import { pushResourceTypeDef } from "./resource-type.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
async function pull(opts: GlobalOptions) {
|
||||
@@ -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 }[]) =>
|
||||
@@ -62,10 +56,14 @@ async function pull(opts: GlobalOptions) {
|
||||
const x of list
|
||||
) {
|
||||
console.log("syncing " + x.name);
|
||||
const f = new ResourceTypeFile();
|
||||
f.description = x.description;
|
||||
f.schema = JSON.parse(x.schema);
|
||||
await f.push(workspace.workspaceId, x.name);
|
||||
await pushResourceTypeDef(
|
||||
workspace.workspaceId,
|
||||
x.name,
|
||||
{
|
||||
description: x.description,
|
||||
schema: JSON.parse(x.schema),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
cli/main.ts
18
cli/main.ts
@@ -8,14 +8,13 @@ import variable from "./variable.ts";
|
||||
import push from "./push.ts";
|
||||
import pull from "./pull.ts";
|
||||
import hub from "./hub.ts";
|
||||
import folder from "./folder.ts";
|
||||
import sync from "./sync.ts";
|
||||
// import folder from "./folder.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
const VERSION = "v1.63.1";
|
||||
const VERSION = "v1.61.1";
|
||||
|
||||
let command: any = new Command()
|
||||
const command = new Command()
|
||||
.name("wmill")
|
||||
.description("A simple CLI tool for windmill.")
|
||||
.globalOption(
|
||||
@@ -33,9 +32,10 @@ let command: any = new Command()
|
||||
.command("resource", resource)
|
||||
.command("user", user)
|
||||
.command("variable", variable)
|
||||
.command("push", push)
|
||||
.command("pull", pull)
|
||||
.command("hub", hub)
|
||||
.command("folder", folder)
|
||||
.command("sync", sync)
|
||||
// .command("folder", folder)
|
||||
.command("version", "Show version information")
|
||||
.action(async (opts) => {
|
||||
console.log("CLI build against " + VERSION);
|
||||
@@ -61,12 +61,6 @@ let command: any = new Command()
|
||||
}),
|
||||
);
|
||||
|
||||
if (Number.parseInt(VERSION.replace("v", "").replace(".", "")) > 1700) {
|
||||
command = command
|
||||
.command("push", push)
|
||||
.command("pull", pull);
|
||||
}
|
||||
|
||||
try {
|
||||
await command.parse(Deno.args);
|
||||
} catch (e) {
|
||||
|
||||
71
cli/pull.ts
71
cli/pull.ts
@@ -1,11 +1,20 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { resolveWorkspace } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { colors, Command, readerFromStreamReader, Untar } from "./deps.ts";
|
||||
import { Workspace } from "./workspace.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
Confirm,
|
||||
copy,
|
||||
ensureDir,
|
||||
path,
|
||||
readerFromStreamReader,
|
||||
Untar,
|
||||
} from "./deps.ts";
|
||||
|
||||
async function pull(opts: GlobalOptions & { override: boolean }, dir: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
|
||||
export async function downloadTar(
|
||||
workspace: Workspace,
|
||||
): Promise<Untar | undefined> {
|
||||
const requestHeaders: HeadersInit = new Headers();
|
||||
requestHeaders.set("Authorization", "Bearer " + workspace.token);
|
||||
requestHeaders.set("Content-Type", "application/octet-stream");
|
||||
@@ -26,29 +35,51 @@ export async function downloadTar(
|
||||
),
|
||||
);
|
||||
console.log(await tarResponse.text());
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const streamReader = tarResponse.body?.getReader();
|
||||
if (!streamReader) {
|
||||
console.log(colors.red("Failed to read tar request body"));
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
console.log(colors.yellow("Streaming tarball to disk..."));
|
||||
const denoReader = readerFromStreamReader(streamReader);
|
||||
const untar = new Untar(denoReader);
|
||||
return untar;
|
||||
}
|
||||
|
||||
async function stub(
|
||||
_opts: GlobalOptions & { override: boolean },
|
||||
_dir: string,
|
||||
) {
|
||||
console.log(
|
||||
colors.red.underline(
|
||||
'Pull is deprecated. Use "sync pull --raw" instead. See <TODO_LINK_HERE> for more information.',
|
||||
),
|
||||
);
|
||||
for await (const entry of untar) {
|
||||
console.log(entry.fileName);
|
||||
const filePath = path.resolve(dir, entry.fileName);
|
||||
if (entry.type === "directory") {
|
||||
await ensureDir(filePath);
|
||||
continue;
|
||||
}
|
||||
await ensureDir(path.dirname(filePath));
|
||||
if (!opts.override) {
|
||||
let exists = false;
|
||||
try {
|
||||
const _stat = await Deno.stat(filePath);
|
||||
exists = true;
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
if (exists) {
|
||||
if (
|
||||
!(await Confirm.prompt(
|
||||
"Conflict at " +
|
||||
filePath +
|
||||
" do you want to override the local version?",
|
||||
))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
const file = await Deno.open(filePath, { write: true, create: true });
|
||||
const len = await copy(entry, file);
|
||||
await file.truncate(len);
|
||||
file.close();
|
||||
}
|
||||
console.log(colors.green("Done. Wrote all files to disk."));
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
@@ -56,6 +87,6 @@ const command = new Command()
|
||||
"Pull all definitions in the current workspace from the API and write them to disk.",
|
||||
)
|
||||
.arguments("<dir:string>")
|
||||
.action(stub as any);
|
||||
.action(pull as any);
|
||||
|
||||
export default command;
|
||||
|
||||
261
cli/push.ts
261
cli/push.ts
@@ -1,21 +1,266 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { colors, Command } from "./deps.ts";
|
||||
import { colors, Command, path } from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { pushFlow } from "./flow.ts";
|
||||
import { pushResource } from "./resource.ts";
|
||||
import { findContentFile, pushScript } from "./script.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { pushVariable } from "./variable.ts";
|
||||
import { pushResourceType } from "./resource-type.ts";
|
||||
import { pushFolder } from "./folder.ts";
|
||||
|
||||
async function stub(
|
||||
_opts: GlobalOptions,
|
||||
_dir?: string,
|
||||
) {
|
||||
type Candidate = {
|
||||
path: string;
|
||||
namespaceKind: "user" | "group" | "folder";
|
||||
namespaceName: string;
|
||||
};
|
||||
|
||||
type ResourceTypeCandidate = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
type FolderCandidate = {
|
||||
path: string;
|
||||
namespaceName: string;
|
||||
};
|
||||
|
||||
async function findCandidateFiles(
|
||||
dir: string,
|
||||
): Promise<
|
||||
{
|
||||
normal: Candidate[];
|
||||
resourceTypes: ResourceTypeCandidate[];
|
||||
folders: FolderCandidate[];
|
||||
}
|
||||
> {
|
||||
dir = path.resolve(dir);
|
||||
if (path.dirname(dir).startsWith(".")) {
|
||||
return { normal: [], resourceTypes: [], folders: [] };
|
||||
}
|
||||
const normalCandidates: Candidate[] = [];
|
||||
const resourceTypeCandidates: ResourceTypeCandidate[] = [];
|
||||
const folderCandidates: FolderCandidate[] = [];
|
||||
for await (const e of Deno.readDir(dir)) {
|
||||
if (e.isDirectory) {
|
||||
if (e.name == "u" || e.name == "g" || e.name == "f") { // TODO: Check version for f
|
||||
const newDir = dir + (dir.endsWith("/") ? "" : "/") + e.name;
|
||||
for await (const e2 of Deno.readDir(newDir)) {
|
||||
if (e2.isDirectory) {
|
||||
if (e2.name.startsWith(".")) continue;
|
||||
const namespaceName = e2.name;
|
||||
const stack: string[] = [];
|
||||
{
|
||||
const path = newDir + "/" + namespaceName + "/";
|
||||
stack.push(path);
|
||||
try {
|
||||
await Deno.stat(path + "folder.meta.json");
|
||||
folderCandidates.push({
|
||||
namespaceName,
|
||||
path: path + "folder.meta.json",
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
while (stack.length > 0) {
|
||||
const dir2 = stack.pop()!;
|
||||
for await (const e3 of Deno.readDir(dir2)) {
|
||||
if (e3.isFile) {
|
||||
if (e3.name === "folder.meta.json") continue;
|
||||
normalCandidates.push({
|
||||
path: dir2 + e3.name,
|
||||
namespaceKind: e.name == "g"
|
||||
? "group"
|
||||
: e.name == "u"
|
||||
? "user"
|
||||
: "folder",
|
||||
namespaceName: namespaceName,
|
||||
});
|
||||
} else {
|
||||
stack.push(dir2 + e3.name + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"Including organizational folder " + e.name + " in push!",
|
||||
),
|
||||
);
|
||||
const { normal, resourceTypes, folders } = await findCandidateFiles(
|
||||
path.join(dir, e.name),
|
||||
);
|
||||
normalCandidates.push(...normal);
|
||||
resourceTypeCandidates.push(...resourceTypes);
|
||||
folderCandidates.push(...folders);
|
||||
}
|
||||
} else {
|
||||
// handle root files
|
||||
if (e.name.endsWith(".resource-type.json")) {
|
||||
resourceTypeCandidates.push({
|
||||
path: dir + (dir.endsWith("/") ? "" : "/") + e.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
normal: normalCandidates,
|
||||
folders: folderCandidates,
|
||||
resourceTypes: resourceTypeCandidates,
|
||||
};
|
||||
}
|
||||
|
||||
async function push(opts: GlobalOptions, dir?: string) {
|
||||
dir = dir ?? Deno.cwd();
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log(colors.blue("Searching Directory..."));
|
||||
const { normal, resourceTypes, folders } = await findCandidateFiles(dir);
|
||||
console.log(
|
||||
colors.red.underline(
|
||||
'Push is deprecated. Use "sync push --raw" instead. See <TODO_LINK_HERE> for more information.',
|
||||
colors.blue(
|
||||
"Found " + (normal.length + resourceTypes.length + folders.length) +
|
||||
" candidates",
|
||||
),
|
||||
);
|
||||
for (const resourceType of resourceTypes) {
|
||||
const fileName = resourceType.path.substring(
|
||||
resourceType.path.lastIndexOf("/") + 1,
|
||||
);
|
||||
const fileNameParts = fileName.split(".");
|
||||
// invalid file names, like my.cool.script.script.json. Not valid.
|
||||
if (fileNameParts.length != 3) {
|
||||
console.log(
|
||||
colors.yellow("invalid file name found at " + resourceType.path),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter out non-json files. Note that we filter out script contents above, so this is really an error.
|
||||
if (fileNameParts.at(-1) != "json") {
|
||||
console.log(colors.yellow("non-JSON file found at " + resourceType.path));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log("pushing resource type " + fileNameParts.at(-3)!);
|
||||
await pushResourceType(
|
||||
workspace.workspaceId,
|
||||
resourceType.path,
|
||||
fileNameParts.at(-3)!,
|
||||
);
|
||||
}
|
||||
for (const folder of folders) {
|
||||
await pushFolder(
|
||||
workspace.workspaceId,
|
||||
folder.path,
|
||||
"f/" + folder.namespaceName,
|
||||
);
|
||||
}
|
||||
for (const candidate of normal) {
|
||||
// full file name. No leading /. includes .type.json
|
||||
const fileName = candidate.path.substring(
|
||||
candidate.path.lastIndexOf("/") + 1,
|
||||
);
|
||||
// figure out just the path after ...../u|g/username|group/ (in extra dir)
|
||||
const dirParts = candidate.path.split("/").filter((x) => x.length > 0);
|
||||
// TODO: check version for folder
|
||||
const gIndex = dirParts.findIndex((x) => x == "u" || x == "g" || x == "f");
|
||||
const extraDir = dirParts.slice(gIndex + 2, -1).join("/");
|
||||
|
||||
// file name parts has .json (hopefully) at -1, type at -2, and the actual name at -3. Dots in names are not allowed.
|
||||
const fileNameParts = fileName.split(".");
|
||||
|
||||
// filter out script content files
|
||||
if (
|
||||
fileNameParts.at(-1) == "ts" ||
|
||||
fileNameParts.at(-1) == "py" ||
|
||||
fileNameParts.at(-1) == "go"
|
||||
) {
|
||||
// probably part of a script. Silent ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
// invalid file names, like my.cool.script.script.json. Not valid.
|
||||
if (fileNameParts.length != 3) {
|
||||
console.log(
|
||||
colors.yellow("invalid file name found at " + candidate.path),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter out non-json files. Note that we filter out script contents above, so this is really an error.
|
||||
if (fileNameParts.at(-1) != "json") {
|
||||
console.log(colors.yellow("non-JSON file found at " + candidate.path));
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the type & filter it for valid ones.
|
||||
const type = fileNameParts.at(-2);
|
||||
if (type == "resource-type") {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"Found resource type file at " +
|
||||
candidate.path +
|
||||
" this appears to be inside a path folder. Resource types are not addressed by path. Place them at the root or inside only an organizational folder. Ignoring this file!",
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
type != "flow" &&
|
||||
type != "resource" &&
|
||||
type != "script" &&
|
||||
type != "variable"
|
||||
) {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"file with invalid type " + type + " found at " + candidate.path,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// create the remotePath for the API
|
||||
const remotePath = (candidate.namespaceKind === "group"
|
||||
? "g/"
|
||||
: (candidate.namespaceKind === "user" ? "u/" : "f/")) +
|
||||
candidate.namespaceName +
|
||||
"/" +
|
||||
(extraDir.length > 0 ? extraDir + "/" : "") +
|
||||
fileNameParts.at(-3);
|
||||
|
||||
console.log("pushing " + type + " to " + remotePath);
|
||||
|
||||
if (type == "flow") {
|
||||
await pushFlow(candidate.path, workspace.workspaceId, remotePath);
|
||||
} else if (type == "resource") {
|
||||
await pushResource(workspace.workspaceId, candidate.path, remotePath);
|
||||
} else if (type == "script") {
|
||||
let contentPath: string;
|
||||
try {
|
||||
contentPath = await findContentFile(candidate.path);
|
||||
} catch (e) {
|
||||
console.log(colors.red(e.toString()));
|
||||
continue;
|
||||
}
|
||||
await pushScript(
|
||||
candidate.path,
|
||||
contentPath,
|
||||
workspace.workspaceId,
|
||||
remotePath,
|
||||
);
|
||||
} else if (type == "variable") {
|
||||
await pushVariable(workspace.workspaceId, candidate.path, remotePath);
|
||||
}
|
||||
}
|
||||
console.log(colors.underline.bold.green("Successfully Pushed all files."));
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
.description("Push all files from a folder")
|
||||
.arguments("[dir:string]")
|
||||
.action(stub as any);
|
||||
.action(push as any);
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -1,129 +1,65 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import {
|
||||
Difference,
|
||||
GlobalOptions,
|
||||
PushDiffs,
|
||||
Resource as ResourceI,
|
||||
setValueByPath,
|
||||
} from "./types.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
EditResourceType,
|
||||
microdiff,
|
||||
ResourceService,
|
||||
ResourceType,
|
||||
Table,
|
||||
} from "./deps.ts";
|
||||
import { Any, decoverto, model, property } from "./decoverto.ts";
|
||||
import { colors, Command, ResourceService, Table } from "./deps.ts";
|
||||
|
||||
@model()
|
||||
export class ResourceTypeFile implements ResourceI, PushDiffs {
|
||||
@property(Any)
|
||||
type ResourceTypeFile = {
|
||||
schema?: any;
|
||||
@property(() => String)
|
||||
description?: string;
|
||||
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: ResourceType | undefined;
|
||||
try {
|
||||
existing = await ResourceService.getResourceType({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (
|
||||
await ResourceService.existsResourceType({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
})
|
||||
) {
|
||||
if (
|
||||
(await ResourceService.listResourceType({ workspace })).findIndex((x) =>
|
||||
x.name === remotePath
|
||||
) === -1
|
||||
) {
|
||||
console.log(
|
||||
"Resource type " + remotePath +
|
||||
" is already taken for the current workspace, but cannot be updated. Is this a conflict with starter?",
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
colors.yellow(
|
||||
`Applying ${diffs.length} diffs to existing resource type...`,
|
||||
),
|
||||
);
|
||||
const changeset: EditResourceType = {};
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path.length !== 1 ||
|
||||
!["schema", "description"].includes(diff.path[0] as string)
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid resource type 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) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
await ResourceService.updateResourceType({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow("Creating new resource type..."));
|
||||
await ResourceService.createResourceType({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
name: remotePath,
|
||||
description: this.description,
|
||||
schema: this.schema,
|
||||
workspace_id: workspace,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export async function pushResourceType(
|
||||
workspace: string,
|
||||
filePath: string,
|
||||
name: string,
|
||||
) {
|
||||
const data: ResourceTypeFile = decoverto.type(ResourceTypeFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
await data.push(workspace, name);
|
||||
const data: ResourceTypeFile = JSON.parse(await Deno.readTextFile(filePath));
|
||||
await pushResourceTypeDef(workspace, name, data);
|
||||
}
|
||||
|
||||
export async function pushResourceTypeDef(
|
||||
workspace: string,
|
||||
name: string,
|
||||
data: ResourceTypeFile,
|
||||
) {
|
||||
if (
|
||||
await ResourceService.existsResourceType({
|
||||
workspace: workspace,
|
||||
path: name,
|
||||
})
|
||||
) {
|
||||
console.log(colors.yellow("Updating existing resource type..."));
|
||||
if (
|
||||
(await ResourceService.listResourceType({ workspace })).findIndex((x) =>
|
||||
x.name === name
|
||||
) === -1
|
||||
) {
|
||||
console.log(
|
||||
"Resource type " + name +
|
||||
" is already taken for the current workspace, but cannot be updated. Is this a conflict with starter?",
|
||||
);
|
||||
return;
|
||||
}
|
||||
await ResourceService.updateResourceType({
|
||||
workspace: workspace,
|
||||
path: name,
|
||||
requestBody: {
|
||||
description: data.description,
|
||||
schema: data.schema,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow("Creating new resource type..."));
|
||||
await ResourceService.createResourceType({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
name: name,
|
||||
description: data.description,
|
||||
schema: data.schema,
|
||||
workspace_id: workspace,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type PushOptions = GlobalOptions;
|
||||
|
||||
194
cli/resource.ts
194
cli/resource.ts
@@ -1,142 +1,80 @@
|
||||
import {
|
||||
Difference,
|
||||
GlobalOptions,
|
||||
PushDiffs,
|
||||
Resource as Resource2,
|
||||
setValueByPath,
|
||||
} from "./types.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
EditResource,
|
||||
microdiff,
|
||||
Resource,
|
||||
ResourceService,
|
||||
Table,
|
||||
} from "./deps.ts";
|
||||
import { Any, decoverto, model, property } from "./decoverto.ts";
|
||||
import { colors, Command, Resource, ResourceService, Table } from "./deps.ts";
|
||||
|
||||
@model()
|
||||
export class ResourceFile implements Resource2, PushDiffs {
|
||||
@property(Any)
|
||||
value?: any;
|
||||
@property(() => String)
|
||||
type ResourceFile = {
|
||||
value: any;
|
||||
description?: string;
|
||||
@property(() => String)
|
||||
resource_type: string;
|
||||
@property(() => Boolean)
|
||||
is_oauth?: boolean; // deprecated
|
||||
|
||||
constructor(resource_type: string) {
|
||||
this.resource_type = resource_type;
|
||||
}
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (
|
||||
await ResourceService.existsResource({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
})
|
||||
) {
|
||||
console.log(
|
||||
colors.yellow(`Applying ${diffs.length} diffs to existing resource...`),
|
||||
);
|
||||
|
||||
const changeset: EditResource = {
|
||||
path: remotePath, // TODO: Remove this in backend
|
||||
};
|
||||
for (const diff of diffs) {
|
||||
if (diff.path[0] === "is_oauth") {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"! is_oauth has been removed in newer versions. Ignoring.",
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path[0] !== "value" && (
|
||||
diff.path.length !== 1 ||
|
||||
diff.path[0] !== "description"
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid folder 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) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
await ResourceService.updateResource({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
if (typeof this.is_oauth !== "undefined") {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"! is_oauth has been removed in newer versions. Ignoring.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(colors.yellow("Creating new resource..."));
|
||||
await ResourceService.createResource({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
resource_type: this.resource_type,
|
||||
value: this.value,
|
||||
description: this.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: Resource | undefined;
|
||||
try {
|
||||
existing = await ResourceService.getResource({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export async function pushResource(
|
||||
workspace: string,
|
||||
filePath: string,
|
||||
remotePath: string,
|
||||
) {
|
||||
const data = decoverto.type(ResourceFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
await data.push(workspace, remotePath);
|
||||
const data: ResourceFile = JSON.parse(await Deno.readTextFile(filePath));
|
||||
if (
|
||||
await ResourceService.existsResource({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
})
|
||||
) {
|
||||
console.log(colors.yellow("Updating existing resource..."));
|
||||
const existing = await ResourceService.getResource({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
|
||||
if (existing.resource_type != data.resource_type) {
|
||||
console.log(
|
||||
colors.red.underline.bold(
|
||||
"Remote resource at " +
|
||||
remotePath +
|
||||
" exists & has a different resource type. This cannot be updated. If you wish to do this anyways, consider deleting the remote resource.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data.is_oauth !== "undefined") {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"! is_oauth has been removed in newer versions. Ignoring.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await ResourceService.updateResource({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
value: data.value,
|
||||
description: data.description,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (typeof data.is_oauth !== "undefined") {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"! is_oauth has been removed in newer versions. Ignoring.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(colors.yellow("Creating new resource..."));
|
||||
await ResourceService.createResource({
|
||||
workspace: workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
resource_type: data.resource_type,
|
||||
value: data.value,
|
||||
description: data.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type PushOptions = GlobalOptions;
|
||||
|
||||
@@ -2,52 +2,20 @@
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
JobService,
|
||||
readAll,
|
||||
Script,
|
||||
ScriptService,
|
||||
Table,
|
||||
} from "./deps.ts";
|
||||
import { Any, array, decoverto, model, property } from "./decoverto.ts";
|
||||
} from "https://deno.land/x/windmill@v1.50.0/windmill-api/index.ts";
|
||||
import { colors, Command, readAll, ScriptService, Table } from "./deps.ts";
|
||||
|
||||
@model()
|
||||
export class ScriptFile {
|
||||
@property(() => String)
|
||||
type ScriptFile = {
|
||||
parent_hash?: string;
|
||||
@property(() => String)
|
||||
summary: string;
|
||||
@property(() => String)
|
||||
description: string;
|
||||
@property(Any)
|
||||
schema?: any;
|
||||
@property(() => Boolean)
|
||||
is_template?: boolean;
|
||||
@property(array(() => String))
|
||||
lock?: Array<string>;
|
||||
@property({
|
||||
toInstance: (data) => {
|
||||
if (data == null) return data;
|
||||
|
||||
if (
|
||||
data === "script" || data === "failure" || data === "trigger" ||
|
||||
data === "command" || data === "approvial"
|
||||
) {
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new Error("Invalid kind " + data);
|
||||
},
|
||||
toPlain: (data) => data,
|
||||
})
|
||||
kind?: "script" | "failure" | "trigger" | "command" | "approval";
|
||||
|
||||
constructor(summary: string, description: string) {
|
||||
this.summary = summary;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type PushOptions = GlobalOptions;
|
||||
async function push(
|
||||
@@ -80,12 +48,10 @@ async function push(
|
||||
}
|
||||
|
||||
export async function findContentFile(filePath: string) {
|
||||
console.log("Searching " + filePath);
|
||||
const candidates = [
|
||||
filePath.replace(".script.json", ".ts"),
|
||||
filePath.replace(".script.json", ".py"),
|
||||
filePath.replace(".script.json", ".go"),
|
||||
filePath.replace(".script.json", ".sh"),
|
||||
];
|
||||
const validCandidates = (
|
||||
await Promise.all(
|
||||
@@ -113,35 +79,23 @@ export async function findContentFile(filePath: string) {
|
||||
return validCandidates[0];
|
||||
}
|
||||
|
||||
export function inferContentTypeFromFilePath(
|
||||
contentPath: string,
|
||||
): "python3" | "deno" | "go" | "bash" {
|
||||
let language = contentPath.substring(contentPath.lastIndexOf("."));
|
||||
if (language == ".ts") language = "deno";
|
||||
if (language == ".py") language = "python3";
|
||||
if (language == ".sh") language = "bash";
|
||||
if (language == ".go") language = "go";
|
||||
if (
|
||||
language != "python3" && language != "deno" && language != "go" &&
|
||||
language != "bash"
|
||||
) {
|
||||
throw new Error("Invalid language: " + language);
|
||||
}
|
||||
return language;
|
||||
}
|
||||
|
||||
export async function pushScript(
|
||||
filePath: string,
|
||||
contentPath: string,
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
) {
|
||||
const data = decoverto.type(ScriptFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
const data: ScriptFile = JSON.parse(await Deno.readTextFile(filePath));
|
||||
const content = await Deno.readTextFile(contentPath);
|
||||
|
||||
const language = inferContentTypeFromFilePath(contentPath);
|
||||
let language = contentPath.substring(contentPath.lastIndexOf("."));
|
||||
if (language == ".ts") language = "deno";
|
||||
if (language == ".py") language = "python3";
|
||||
if (language == ".go") language = "go";
|
||||
if (language != "python3" && language != "deno" && language != "go") {
|
||||
throw new Error("Invalid language: " + language);
|
||||
}
|
||||
|
||||
let parent_hash = data.parent_hash;
|
||||
if (!parent_hash) {
|
||||
try {
|
||||
|
||||
969
cli/sync.ts
969
cli/sync.ts
@@ -1,969 +0,0 @@
|
||||
import { requireLogin, resolveWorkspace } from "./context.ts";
|
||||
import { getWorkspaceStream, Workspace } from "./workspace.ts";
|
||||
import { decoverto, map, MapShape, model, property } from "./decoverto.ts";
|
||||
import {
|
||||
cbor,
|
||||
colors,
|
||||
Command,
|
||||
Confirm,
|
||||
copy,
|
||||
ensureDir,
|
||||
iterateReader,
|
||||
microdiff,
|
||||
nanoid,
|
||||
objectHash,
|
||||
path,
|
||||
ScriptService,
|
||||
} from "./deps.ts";
|
||||
import {
|
||||
Difference,
|
||||
getTypeStrFromPath,
|
||||
GlobalOptions,
|
||||
inferTypeFromPath,
|
||||
setValueByPath,
|
||||
} from "./types.ts";
|
||||
import { downloadTar } from "./pull.ts";
|
||||
import { FolderFile } from "./folder.ts";
|
||||
import { ResourceTypeFile } from "./resource-type.ts";
|
||||
import {
|
||||
findContentFile,
|
||||
inferContentTypeFromFilePath,
|
||||
pushScript,
|
||||
ScriptFile,
|
||||
} from "./script.ts";
|
||||
import { ResourceFile } from "./resource.ts";
|
||||
import { FlowFile } from "./flow.ts";
|
||||
import { VariableFile } from "./variable.ts";
|
||||
|
||||
type TrackedId = string;
|
||||
const TrackedId = String;
|
||||
|
||||
const CONTENT_ENCODER: cbor.Encoder = new cbor.Encoder({ pack: true });
|
||||
|
||||
export class Tracked {
|
||||
#id: TrackedId;
|
||||
#parent: State;
|
||||
|
||||
path: string;
|
||||
|
||||
#content_cache?: string;
|
||||
|
||||
constructor(id: TrackedId, parent: State, path: string) {
|
||||
this.#id = id;
|
||||
this.#parent = parent;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
async getContent(): Promise<unknown | undefined> {
|
||||
if (this.#content_cache) {
|
||||
return this.#content_cache;
|
||||
}
|
||||
|
||||
if (!this.#parent.stateRoot) {
|
||||
throw new Error("Parent uninitialized");
|
||||
}
|
||||
|
||||
const f = this.#parent.contentFile(this.#id);
|
||||
if (!f) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data = await Deno.readFile(
|
||||
path.join(this.#parent.stateRoot, ".wmill", f),
|
||||
);
|
||||
const content = CONTENT_ENCODER.decode(data);
|
||||
this.#content_cache = content;
|
||||
return content;
|
||||
}
|
||||
|
||||
getHash(): string {
|
||||
return this.#parent.hashes.get(this.#id)!;
|
||||
}
|
||||
|
||||
getId(): TrackedId {
|
||||
return this.#id;
|
||||
}
|
||||
}
|
||||
|
||||
@model()
|
||||
export class State {
|
||||
// TODO: Handle script contents, as they are separate files & need to be tracked separately.
|
||||
// I think the way this makes most sense is actually reating them as a separate kind of file everywhere
|
||||
// And then move to using a diff-based push system everywhere. This should greatly simplify code & enable this easily
|
||||
|
||||
@property(map(() => TrackedId, () => String, { shape: MapShape.Object }))
|
||||
hashes: Map<TrackedId, string>;
|
||||
|
||||
@property(map(() => TrackedId, () => String, { shape: MapShape.Object }))
|
||||
contentFiles: Map<TrackedId, string>;
|
||||
|
||||
@property(map(() => String, () => TrackedId, { shape: MapShape.Object }))
|
||||
tracked: Map<string, TrackedId>;
|
||||
|
||||
@property(() => String)
|
||||
workspaceId: string;
|
||||
|
||||
@property(() => String)
|
||||
remoteUrl: string;
|
||||
|
||||
stateRoot?: string;
|
||||
|
||||
constructor(
|
||||
hashes: Map<TrackedId, string>,
|
||||
tracked: Map<string, TrackedId>,
|
||||
contentFiles: Map<TrackedId, string>,
|
||||
workspaceId: string,
|
||||
remoteUrl: string,
|
||||
) {
|
||||
this.hashes = hashes;
|
||||
this.tracked = tracked;
|
||||
this.contentFiles = contentFiles;
|
||||
this.workspaceId = workspaceId;
|
||||
this.remoteUrl = remoteUrl;
|
||||
}
|
||||
|
||||
add(path: string) {
|
||||
if (this.tracked.get(path)) {
|
||||
throw new Error("Cannot newly track already tracked paths");
|
||||
} else {
|
||||
this.tracked.set(path, nanoid());
|
||||
}
|
||||
}
|
||||
|
||||
public forget(path: string) {
|
||||
const id = this.tracked.get(path);
|
||||
if (id) {
|
||||
this.tracked.delete(path);
|
||||
this.hashes.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
public contentFile(id: TrackedId): string | undefined {
|
||||
return this.contentFiles.get(id);
|
||||
}
|
||||
|
||||
public get(path: string): Tracked {
|
||||
const id = this.tracked.get(path);
|
||||
if (!id) {
|
||||
throw new Error("Could not resolve path " + path);
|
||||
}
|
||||
return new Tracked(id, this, path);
|
||||
}
|
||||
|
||||
public async save(): Promise<void> {
|
||||
if (!this.stateRoot) {
|
||||
throw new Error("Uninitialized state root");
|
||||
}
|
||||
const encoder = new cbor.Encoder({});
|
||||
const plain = decoverto.type(State).instanceToPlain(this);
|
||||
const result: Uint8Array = encoder.encode(plain, {});
|
||||
await Deno.writeFile(path.join(this.stateRoot, ".wmill", "main"), result, {
|
||||
create: true,
|
||||
});
|
||||
}
|
||||
|
||||
public static async loadState(dir: string): Promise<State> {
|
||||
const source = await Deno.readFile(path.join(dir, ".wmill", "main"));
|
||||
const encoder = new cbor.Encoder({});
|
||||
const raw = encoder.decode(source);
|
||||
const state = decoverto.type(State).plainToInstance(
|
||||
raw,
|
||||
);
|
||||
state.stateRoot = dir;
|
||||
return Object.freeze(state);
|
||||
}
|
||||
}
|
||||
|
||||
async function getState(opts: GlobalOptions) {
|
||||
const existingState = await State.loadState(Deno.cwd());
|
||||
|
||||
const workspaceStream = await getWorkspaceStream();
|
||||
const reader = workspaceStream.getReader();
|
||||
while (true) {
|
||||
const res = await reader.read();
|
||||
if (res.value) {
|
||||
if (
|
||||
new URL(res.value.remote) == new URL(existingState.remoteUrl) &&
|
||||
res.value.workspaceId == existingState.workspaceId
|
||||
) {
|
||||
opts.workspace = res.value.name;
|
||||
(opts as any).__secret_workspace = res.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (res.done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return existingState;
|
||||
}
|
||||
|
||||
async function updateStateFromRemote(
|
||||
workspace: Workspace,
|
||||
state: State,
|
||||
callback: (filename: string) => PromiseLike<boolean> | boolean,
|
||||
) {
|
||||
const untar = await downloadTar(workspace);
|
||||
if (!untar) throw new Error("Failed to pull Tar");
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
for await (const entry of untar) {
|
||||
const id = state.tracked.get(entry.fileName);
|
||||
if (id) {
|
||||
let val = "";
|
||||
for await (const e of iterateReader(entry)) {
|
||||
const tmp = decoder.decode(e);
|
||||
val += tmp;
|
||||
}
|
||||
|
||||
if (entry.fileName.endsWith(".json")) {
|
||||
const parsed = JSON.parse(val);
|
||||
const typed = inferTypeFromPath(entry.fileName, parsed);
|
||||
|
||||
const oldHash = state.hashes.get(id);
|
||||
const newHash = objectHash(typed);
|
||||
|
||||
if (!oldHash || oldHash !== newHash) {
|
||||
if (!await callback(entry.fileName)) {
|
||||
return; // notice that we are not saving
|
||||
}
|
||||
state.hashes.set(id, newHash);
|
||||
|
||||
const encoded = CONTENT_ENCODER.encode(typed);
|
||||
|
||||
const fileName = nanoid();
|
||||
await Deno.writeFile(
|
||||
path.join(state.stateRoot!, ".wmill", fileName),
|
||||
encoded,
|
||||
{ create: true },
|
||||
);
|
||||
state.contentFiles.set(id, fileName);
|
||||
}
|
||||
} else {
|
||||
const fileName = nanoid();
|
||||
await Deno.writeTextFile(
|
||||
path.join(state.stateRoot!, ".wmill", fileName),
|
||||
val,
|
||||
{ create: true },
|
||||
);
|
||||
state.contentFiles.set(id, fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await state.save();
|
||||
}
|
||||
|
||||
async function pull(
|
||||
opts: GlobalOptions & { raw: boolean; rawOverride: boolean },
|
||||
) {
|
||||
if (opts.raw) {
|
||||
const opts2 = opts as any;
|
||||
opts2.override = opts2.rawOverride;
|
||||
opts2.raw = undefined;
|
||||
opts2.rawOverride = undefined;
|
||||
await pullRaw(opts2, Deno.cwd());
|
||||
return;
|
||||
}
|
||||
|
||||
const state = await getState(opts);
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log("Pulling remote changes");
|
||||
await updateStateFromRemote(workspace, state, (_) => true);
|
||||
|
||||
const diffs = diffState(state);
|
||||
|
||||
await copyNonJsonFiles(state);
|
||||
|
||||
console.log(`Applying changes to files`);
|
||||
for await (const diff of diffs) {
|
||||
await applyDiff(
|
||||
diff.diff,
|
||||
path.join(state.stateRoot!, diff.localPath),
|
||||
);
|
||||
}
|
||||
console.log(colors.green.underline("Done! All changes applied."));
|
||||
|
||||
async function applyDiff(diffs: Difference[], file: string) {
|
||||
ensureDir(path.dirname(file));
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(await Deno.readTextFile(file));
|
||||
} catch {
|
||||
json = {};
|
||||
}
|
||||
// TODO: Delegate the below to the object itself
|
||||
// This would work by infering the type of `JSON` (which includes then statically typing it using decoverto) and then
|
||||
// delegating the applying of the diffs to the object via an interface
|
||||
for (const diff of diffs) {
|
||||
if (diff.type === "CREATE") {
|
||||
setValueByPath(json, diff.path, diff.value);
|
||||
} else if (diff.type === "REMOVE") {
|
||||
setValueByPath(json, diff.path, undefined);
|
||||
} else if (diff.type === "CHANGE") {
|
||||
setValueByPath(json, diff.path, diff.value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use decoverto instanceToPlain below.
|
||||
await Deno.writeTextFile(file, JSON.stringify(json, undefined, " "), {
|
||||
create: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function copyNonJsonFiles(state: State): Promise<void> {
|
||||
for (const t of state.tracked.keys()) {
|
||||
if (t.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
const entry = state.get(t);
|
||||
|
||||
const target = await Deno.open(entry.path, { create: true, write: true });
|
||||
const source = await Deno.open(
|
||||
path.join(
|
||||
state.stateRoot!,
|
||||
".wmill",
|
||||
state.contentFile(entry.getId())!,
|
||||
),
|
||||
{
|
||||
read: true,
|
||||
write: false,
|
||||
},
|
||||
);
|
||||
await source.readable.pipeTo(target.writable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function* diffState(state: State): AsyncGenerator<StateDiff, void, void> {
|
||||
for (const t of state.tracked.keys()) {
|
||||
if (!t.endsWith(".json")) {
|
||||
continue;
|
||||
}
|
||||
const entry = state.get(t);
|
||||
let fileText;
|
||||
try {
|
||||
fileText = await Deno.readTextFile(
|
||||
path.join(state.stateRoot!, entry.path),
|
||||
);
|
||||
} catch {
|
||||
fileText = "{}";
|
||||
}
|
||||
const old = JSON.parse(fileText);
|
||||
const fileHash = objectHash(old);
|
||||
|
||||
if (fileHash !== entry.getHash()) {
|
||||
const stateContent = await entry.getContent() as any;
|
||||
|
||||
const diff = microdiff(
|
||||
old,
|
||||
stateContent ?? {},
|
||||
{ cyclesFix: false },
|
||||
);
|
||||
|
||||
yield new StateDiff(entry.getId(), entry.path, diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prettyDiff(diffs: Difference[]) {
|
||||
for (const diff of diffs) {
|
||||
let pathString = "";
|
||||
for (const pathSegment of diff.path) {
|
||||
if (typeof pathSegment === "string") {
|
||||
pathString += ".";
|
||||
pathString += pathSegment;
|
||||
} else {
|
||||
pathString += "[";
|
||||
pathString += pathSegment;
|
||||
pathString += "]";
|
||||
}
|
||||
}
|
||||
if (diff.type === "REMOVE" || diff.type === "CHANGE") {
|
||||
console.log(colors.red("- " + pathString + " = " + diff.oldValue));
|
||||
}
|
||||
if (diff.type === "CREATE" || diff.type === "CHANGE") {
|
||||
console.log(colors.green("+ " + pathString + " = " + diff.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function push(opts: GlobalOptions & { raw: boolean }) {
|
||||
if (opts.raw) {
|
||||
const opts2 = opts as any;
|
||||
opts2.raw = undefined;
|
||||
await pushRaw(opts2, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = await getState(opts);
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
let error = false;
|
||||
await updateStateFromRemote(workspace, state, async (filename) => {
|
||||
const e = state.get(filename);
|
||||
if (!e) {
|
||||
throw new Error("!? State change on untracked file ?!");
|
||||
}
|
||||
|
||||
let fileJSON;
|
||||
try {
|
||||
fileJSON = JSON.parse(
|
||||
await Deno.readTextFile(path.join(state.stateRoot!, e.path)),
|
||||
);
|
||||
} catch {
|
||||
fileJSON = {};
|
||||
}
|
||||
const file = inferTypeFromPath(e.path, fileJSON);
|
||||
const eContent = (inferTypeFromPath(e.path, await e.getContent())) ?? {};
|
||||
|
||||
const fileHash = objectHash(file);
|
||||
const eHash = objectHash(eContent);
|
||||
|
||||
if (fileHash !== eHash) {
|
||||
console.log(
|
||||
colors.red("!! Local and Remote change present. Local diff:"),
|
||||
);
|
||||
prettyDiff(microdiff(eContent as any, file, { cyclesFix: false }));
|
||||
console.log(
|
||||
colors.red(
|
||||
"Consider comitting or otherwise saving your work and pulling to load any remote changes",
|
||||
),
|
||||
);
|
||||
error = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const p of state.tracked.keys()) {
|
||||
if (!p.endsWith(".json")) continue;
|
||||
const entry = state.get(p);
|
||||
let fileJSON;
|
||||
try {
|
||||
fileJSON = JSON.parse(
|
||||
await Deno.readTextFile(path.join(state.stateRoot!, entry.path)),
|
||||
);
|
||||
} catch {
|
||||
fileJSON = {};
|
||||
}
|
||||
const file = inferTypeFromPath(entry.path, fileJSON);
|
||||
const eContent =
|
||||
(inferTypeFromPath(entry.path, await entry.getContent())) ?? {};
|
||||
|
||||
const fileHash = objectHash(file);
|
||||
const eHash = objectHash(eContent);
|
||||
|
||||
if (fileHash !== eHash) {
|
||||
const remotePath = entry.path.split(".")[0];
|
||||
const type = getTypeStrFromPath(entry.path);
|
||||
if (type === "script") {
|
||||
// Diffing makes no sense for scripts - instead fetch parent hash & check hash again.
|
||||
// If hash is still missmatched - create new script as child.
|
||||
const typed = decoverto.type(ScriptFile).plainToInstance(file);
|
||||
const contentPath = await findContentFile(entry.path);
|
||||
const language = inferContentTypeFromFilePath(contentPath);
|
||||
const content = await Deno.readTextFile(contentPath);
|
||||
try {
|
||||
const remote = await ScriptService.getScriptByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path: remotePath,
|
||||
});
|
||||
if (objectHash(remote) !== fileHash) {
|
||||
await ScriptService.createScript({
|
||||
workspace: workspace.workspaceId,
|
||||
requestBody: {
|
||||
content,
|
||||
description: typed.description,
|
||||
language,
|
||||
path: remotePath,
|
||||
summary: typed.summary,
|
||||
is_template: typed.is_template,
|
||||
kind: typed.kind,
|
||||
lock: undefined,
|
||||
parent_hash: remote.hash,
|
||||
schema: typed.schema,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// no parent hash
|
||||
await ScriptService.createScript({
|
||||
workspace: workspace.workspaceId,
|
||||
requestBody: {
|
||||
content,
|
||||
description: typed.description,
|
||||
language,
|
||||
path: remotePath,
|
||||
summary: typed.summary,
|
||||
is_template: typed.is_template,
|
||||
kind: typed.kind,
|
||||
lock: undefined,
|
||||
parent_hash: undefined,
|
||||
schema: typed.schema,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const diff = microdiff(eContent as any, file, { cyclesFix: false });
|
||||
await applyDiff(
|
||||
workspace.workspaceId,
|
||||
remotePath,
|
||||
file,
|
||||
diff,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let anyRemoteChanges = false;
|
||||
await updateStateFromRemote(workspace, state, (_) => {
|
||||
anyRemoteChanges = true;
|
||||
return true;
|
||||
});
|
||||
if (anyRemoteChanges) {
|
||||
console.log("New remote changes - consider pulling");
|
||||
}
|
||||
|
||||
function applyDiff(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
file:
|
||||
| ScriptFile
|
||||
| VariableFile
|
||||
| FlowFile
|
||||
| ResourceFile
|
||||
| ResourceTypeFile
|
||||
| FolderFile,
|
||||
diffs: Difference[],
|
||||
) {
|
||||
if (file instanceof ScriptFile) {
|
||||
throw new Error(
|
||||
"This code path should be unreachable - we should never generate diffs for scripts",
|
||||
);
|
||||
} else if (file instanceof FolderFile) {
|
||||
const parts = remotePath.split("/");
|
||||
if (parts[0] === "f") {
|
||||
remotePath = parts[1];
|
||||
} else {
|
||||
remotePath = parts[0];
|
||||
}
|
||||
}
|
||||
return file.pushDiffs(workspace, remotePath, diffs);
|
||||
}
|
||||
}
|
||||
|
||||
class StateDiff {
|
||||
trackedId: TrackedId;
|
||||
localPath: string;
|
||||
diff: Difference[];
|
||||
|
||||
constructor(
|
||||
trackedId: TrackedId,
|
||||
localPath: string,
|
||||
diff: Difference[],
|
||||
) {
|
||||
this.trackedId = trackedId;
|
||||
this.localPath = localPath;
|
||||
this.diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
async function add(opts: GlobalOptions, path: string) {
|
||||
const state = await getState(opts);
|
||||
|
||||
// TODO: Automatically check whether this path exists either locally or on the remote
|
||||
state.add(path);
|
||||
|
||||
if (path.endsWith(".script.json")) {
|
||||
try {
|
||||
const f = await findContentFile(path);
|
||||
state.add(f);
|
||||
} catch {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
try {
|
||||
const old = await ScriptService.getScriptByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path: path.split(".")[0],
|
||||
});
|
||||
if (old.language === "python3") {
|
||||
state.add(path.replace(".script.json", ".py"));
|
||||
} else if (old.language === "bash") {
|
||||
state.add(path.replace(".script.json", ".sh"));
|
||||
} else if (old.language === "deno") {
|
||||
state.add(path.replace(".script.json", ".ts"));
|
||||
} else if (old.language === "go") {
|
||||
state.add(path.replace(".script.json", ".go"));
|
||||
} else {
|
||||
throw new Error("Remote returned invalid language?! " + old.language);
|
||||
}
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Could not infer script language from local or remote. Exiting.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await state.save();
|
||||
}
|
||||
|
||||
async function init(opts: GlobalOptions) {
|
||||
const root = Deno.cwd();
|
||||
try {
|
||||
await Deno.mkdir(path.join(root, ".wmill"));
|
||||
} catch {
|
||||
console.log(
|
||||
colors.red(
|
||||
"! Looks like this folder is already initialized or we are missing permissions to do so. Exiting.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
|
||||
const newState = new State(
|
||||
new Map(),
|
||||
new Map(),
|
||||
new Map(),
|
||||
workspace.workspaceId,
|
||||
workspace.remote,
|
||||
);
|
||||
newState.stateRoot = root;
|
||||
|
||||
await newState.save();
|
||||
}
|
||||
|
||||
async function pullRaw(
|
||||
opts: GlobalOptions & { override: boolean },
|
||||
dir: string,
|
||||
) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
|
||||
const untar = await downloadTar(workspace);
|
||||
if (!untar) return;
|
||||
|
||||
for await (const entry of untar) {
|
||||
console.log(entry.fileName);
|
||||
const filePath = path.resolve(dir, entry.fileName);
|
||||
if (entry.type === "directory") {
|
||||
await ensureDir(filePath);
|
||||
continue;
|
||||
}
|
||||
await ensureDir(path.dirname(filePath));
|
||||
if (!opts.override) {
|
||||
let exists = false;
|
||||
try {
|
||||
const _stat = await Deno.stat(filePath);
|
||||
exists = true;
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
if (exists) {
|
||||
if (
|
||||
!(await Confirm.prompt(
|
||||
"Conflict at " +
|
||||
filePath +
|
||||
" do you want to override the local version?",
|
||||
))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
const file = await Deno.open(filePath, { write: true, create: true });
|
||||
const len = await copy(entry, file);
|
||||
await file.truncate(len);
|
||||
file.close();
|
||||
}
|
||||
console.log(colors.green("Done. Wrote all files to disk."));
|
||||
}
|
||||
|
||||
type PushRawCandidate = {
|
||||
path: string;
|
||||
namespaceKind: "user" | "group" | "folder";
|
||||
namespaceName: string;
|
||||
};
|
||||
|
||||
type PushRawResourceTypeCandidate = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
type PushRawFolderCandidate = {
|
||||
path: string;
|
||||
namespaceName: string;
|
||||
};
|
||||
|
||||
async function pushRawFindCandidateFiles(
|
||||
dir: string,
|
||||
): Promise<
|
||||
{
|
||||
normal: PushRawCandidate[];
|
||||
resourceTypes: PushRawResourceTypeCandidate[];
|
||||
folders: PushRawFolderCandidate[];
|
||||
}
|
||||
> {
|
||||
dir = path.resolve(dir);
|
||||
if (path.dirname(dir).startsWith(".")) {
|
||||
return { normal: [], resourceTypes: [], folders: [] };
|
||||
}
|
||||
const normalCandidates: PushRawCandidate[] = [];
|
||||
const resourceTypeCandidates: PushRawResourceTypeCandidate[] = [];
|
||||
const folderCandidates: PushRawFolderCandidate[] = [];
|
||||
for await (const e of Deno.readDir(dir)) {
|
||||
if (e.isDirectory) {
|
||||
if (e.name == "u" || e.name == "g" || e.name == "f") { // TODO: Check version for f
|
||||
const newDir = dir + (dir.endsWith("/") ? "" : "/") + e.name;
|
||||
for await (const e2 of Deno.readDir(newDir)) {
|
||||
if (e2.isDirectory) {
|
||||
if (e2.name.startsWith(".")) continue;
|
||||
const namespaceName = e2.name;
|
||||
const stack: string[] = [];
|
||||
{
|
||||
const path = newDir + "/" + namespaceName + "/";
|
||||
stack.push(path);
|
||||
try {
|
||||
await Deno.stat(path + "folder.meta.json");
|
||||
folderCandidates.push({
|
||||
namespaceName,
|
||||
path: path + "folder.meta.json",
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
while (stack.length > 0) {
|
||||
const dir2 = stack.pop()!;
|
||||
for await (const e3 of Deno.readDir(dir2)) {
|
||||
if (e3.isFile) {
|
||||
if (e3.name === "folder.meta.json") continue;
|
||||
normalCandidates.push({
|
||||
path: dir2 + e3.name,
|
||||
namespaceKind: e.name == "g"
|
||||
? "group"
|
||||
: e.name == "u"
|
||||
? "user"
|
||||
: "folder",
|
||||
namespaceName: namespaceName,
|
||||
});
|
||||
} else {
|
||||
stack.push(dir2 + e3.name + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"Including organizational folder " + e.name + " in push!",
|
||||
),
|
||||
);
|
||||
const { normal, resourceTypes, folders } =
|
||||
await pushRawFindCandidateFiles(
|
||||
path.join(dir, e.name),
|
||||
);
|
||||
normalCandidates.push(...normal);
|
||||
resourceTypeCandidates.push(...resourceTypes);
|
||||
folderCandidates.push(...folders);
|
||||
}
|
||||
} else {
|
||||
// handle root files
|
||||
if (e.name.endsWith(".resource-type.json")) {
|
||||
resourceTypeCandidates.push({
|
||||
path: dir + (dir.endsWith("/") ? "" : "/") + e.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
normal: normalCandidates,
|
||||
folders: folderCandidates,
|
||||
resourceTypes: resourceTypeCandidates,
|
||||
};
|
||||
}
|
||||
|
||||
async function pushRaw(opts: GlobalOptions, dir?: string) {
|
||||
dir = dir ?? Deno.cwd();
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log(colors.blue("Searching Directory..."));
|
||||
const { normal, resourceTypes, folders } = await pushRawFindCandidateFiles(
|
||||
dir,
|
||||
);
|
||||
console.log(
|
||||
colors.blue(
|
||||
"Found " + (normal.length + resourceTypes.length + folders.length) +
|
||||
" candidates",
|
||||
),
|
||||
);
|
||||
for (const resourceType of resourceTypes) {
|
||||
const fileName = resourceType.path.substring(
|
||||
resourceType.path.lastIndexOf("/") + 1,
|
||||
);
|
||||
const fileNameParts = fileName.split(".");
|
||||
// invalid file names, like my.cool.script.script.json. Not valid.
|
||||
if (fileNameParts.length != 3) {
|
||||
console.log(
|
||||
colors.yellow("invalid file name found at " + resourceType.path),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter out non-json files. Note that we filter out script contents above, so this is really an error.
|
||||
if (fileNameParts.at(-1) != "json") {
|
||||
console.log(colors.yellow("non-JSON file found at " + resourceType.path));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log("pushing resource type " + fileNameParts.at(-3)!);
|
||||
await decoverto.type(ResourceTypeFile).rawToInstance(
|
||||
await Deno.readTextFile(resourceType.path),
|
||||
).push(workspace.workspaceId, fileNameParts.at(-3)!);
|
||||
}
|
||||
for (const folder of folders) {
|
||||
await decoverto.type(FolderFile).plainToInstance(
|
||||
JSON.parse(await Deno.readTextFile(folder.path)),
|
||||
).push(
|
||||
workspace.workspaceId,
|
||||
"f/" + folder.namespaceName,
|
||||
);
|
||||
}
|
||||
for (const candidate of normal) {
|
||||
// full file name. No leading /. includes .type.json
|
||||
const fileName = candidate.path.substring(
|
||||
candidate.path.lastIndexOf("/") + 1,
|
||||
);
|
||||
// figure out just the path after ...../u|g/username|group/ (in extra dir)
|
||||
const dirParts = candidate.path.split("/").filter((x) => x.length > 0);
|
||||
// TODO: check version for folder
|
||||
const gIndex = dirParts.findIndex((x) => x == "u" || x == "g" || x == "f");
|
||||
const extraDir = dirParts.slice(gIndex + 2, -1).join("/");
|
||||
|
||||
// file name parts has .json (hopefully) at -1, type at -2, and the actual name at -3. Dots in names are not allowed.
|
||||
const fileNameParts = fileName.split(".");
|
||||
|
||||
// filter out script content files
|
||||
if (
|
||||
fileNameParts.at(-1) == "ts" ||
|
||||
fileNameParts.at(-1) == "py" ||
|
||||
fileNameParts.at(-1) == "go"
|
||||
) {
|
||||
// probably part of a script. Silent ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
// invalid file names, like my.cool.script.script.json. Not valid.
|
||||
if (fileNameParts.length != 3) {
|
||||
console.log(
|
||||
colors.yellow("invalid file name found at " + candidate.path),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter out non-json files. Note that we filter out script contents above, so this is really an error.
|
||||
if (fileNameParts.at(-1) != "json") {
|
||||
console.log(colors.yellow("non-JSON file found at " + candidate.path));
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the type & filter it for valid ones.
|
||||
const type = fileNameParts.at(-2);
|
||||
if (type == "resource-type") {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"Found resource type file at " +
|
||||
candidate.path +
|
||||
" this appears to be inside a path folder. Resource types are not addressed by path. Place them at the root or inside only an organizational folder. Ignoring this file!",
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
type != "flow" &&
|
||||
type != "resource" &&
|
||||
type != "script" &&
|
||||
type != "variable"
|
||||
) {
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"file with invalid type " + type + " found at " + candidate.path,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// create the remotePath for the API
|
||||
const remotePath = (candidate.namespaceKind === "group"
|
||||
? "g/"
|
||||
: (candidate.namespaceKind === "user" ? "u/" : "f/")) +
|
||||
candidate.namespaceName +
|
||||
"/" +
|
||||
(extraDir.length > 0 ? extraDir + "/" : "") +
|
||||
fileNameParts.at(-3);
|
||||
|
||||
console.log("pushing " + type + " to " + remotePath);
|
||||
|
||||
const typed = inferTypeFromPath(
|
||||
candidate.path,
|
||||
JSON.parse(await Deno.readTextFile(candidate.path)),
|
||||
);
|
||||
if (typed instanceof ResourceTypeFile || typed instanceof FolderFile) {
|
||||
throw new Error(
|
||||
"Resource Types and Folders should be filtered out at this point!",
|
||||
);
|
||||
} else if (typed instanceof ScriptFile) {
|
||||
let contentPath: string;
|
||||
try {
|
||||
contentPath = await findContentFile(candidate.path);
|
||||
} catch (e) {
|
||||
console.log(colors.red(e.toString()));
|
||||
continue;
|
||||
}
|
||||
await pushScript(
|
||||
candidate.path,
|
||||
contentPath,
|
||||
workspace.workspaceId,
|
||||
remotePath,
|
||||
);
|
||||
} else {
|
||||
typed.push(workspace.workspaceId, remotePath);
|
||||
}
|
||||
}
|
||||
console.log(colors.underline.bold.green("Successfully Pushed all files."));
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
.command("init")
|
||||
.description(
|
||||
"Initialize this folder as sync root for the currently selected workspace & remote.",
|
||||
)
|
||||
.action(init as any)
|
||||
.command("add")
|
||||
.description("Add a local file for tracking")
|
||||
.arguments("<path:string>")
|
||||
.action(add as any)
|
||||
.command("pull")
|
||||
.description("Pull any remote changes and apply them locally")
|
||||
.option("--raw", "Pull without using state.")
|
||||
.option("--raw-override", "Always override local files with remote.", {
|
||||
depends: ["raw"],
|
||||
})
|
||||
.action(pull as any)
|
||||
.command("push")
|
||||
.description("Push any local changes and apply them remotely")
|
||||
.option("--raw", "Push without using state.")
|
||||
.action(push as any);
|
||||
|
||||
export default command;
|
||||
10
cli/tmp/f/my_folder/folder.meta.json
Normal file
10
cli/tmp/f/my_folder/folder.meta.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"workspace_id": "admins",
|
||||
"name": "my_folder",
|
||||
"display_name": "my_folder",
|
||||
"owners": [],
|
||||
"extra_perms": {
|
||||
"u/test": true,
|
||||
"u/admin@windmill.dev": false
|
||||
}
|
||||
}
|
||||
12
cli/tmp/u/admin/hub_sync.script.json
Normal file
12
cli/tmp/u/admin/hub_sync.script.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"summary": "Syncronize Hub Resource types with starter workspace",
|
||||
"description": "Basic administrative script to sync latest resource types from hub. Recommended to run at least once. On a schedule by default.",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object"
|
||||
},
|
||||
"is_template": false,
|
||||
"lock": []
|
||||
}
|
||||
13
cli/tmp/u/admin/hub_sync.ts
Normal file
13
cli/tmp/u/admin/hub_sync.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import wmill from "https://deno.land/x/wmill@v1.55.0/main.ts";
|
||||
|
||||
export async function main() {
|
||||
await run(
|
||||
"workspace", "add", "__automation", "starter", Deno.env.get("WM_BASE_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);
|
||||
}
|
||||
133
cli/types.ts
133
cli/types.ts
@@ -1,137 +1,4 @@
|
||||
import { decoverto } from "./decoverto.ts";
|
||||
import { FlowFile } from "./flow.ts";
|
||||
import { ResourceTypeFile } from "./resource-type.ts";
|
||||
import { ResourceFile } from "./resource.ts";
|
||||
import { ScriptFile } from "./script.ts";
|
||||
import { VariableFile } from "./variable.ts";
|
||||
import { path } from "./deps.ts";
|
||||
import { FolderFile } from "./folder.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
|
||||
export interface Resource {
|
||||
push(workspace: string, remotePath: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface PushDiffs {
|
||||
pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface DifferenceCreate {
|
||||
type: "CREATE";
|
||||
path: (string | number)[];
|
||||
value: any;
|
||||
}
|
||||
|
||||
export interface DifferenceRemove {
|
||||
type: "REMOVE";
|
||||
path: (string | number)[];
|
||||
oldValue: any;
|
||||
}
|
||||
|
||||
export interface DifferenceChange {
|
||||
type: "CHANGE";
|
||||
path: (string | number)[];
|
||||
value: any;
|
||||
oldValue: any;
|
||||
}
|
||||
|
||||
export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange;
|
||||
|
||||
export function setValueByPath(
|
||||
obj: any,
|
||||
path: (string | number)[],
|
||||
value: any,
|
||||
) {
|
||||
let i;
|
||||
let lastObj = undefined;
|
||||
for (i = 0; i < path.length - 1; i++) {
|
||||
if (!obj) {
|
||||
let oldNewObj;
|
||||
if (typeof path[i] === "number") {
|
||||
oldNewObj = [];
|
||||
} else {
|
||||
oldNewObj = {};
|
||||
}
|
||||
lastObj[path[i - 1]] = oldNewObj;
|
||||
obj = oldNewObj;
|
||||
}
|
||||
lastObj = obj;
|
||||
obj = obj[path[i]];
|
||||
}
|
||||
if (!obj) {
|
||||
let oldNewObj;
|
||||
if (typeof path[i] === "number") {
|
||||
oldNewObj = [];
|
||||
} else {
|
||||
oldNewObj = {};
|
||||
}
|
||||
lastObj[path[i - 1]] = oldNewObj;
|
||||
obj = oldNewObj;
|
||||
}
|
||||
obj[path[i]] = value;
|
||||
}
|
||||
|
||||
export type GlobalOptions = {
|
||||
workspace: string | undefined;
|
||||
token: string | undefined;
|
||||
};
|
||||
|
||||
export function inferTypeFromPath(
|
||||
p: string,
|
||||
obj: any,
|
||||
):
|
||||
| ScriptFile
|
||||
| VariableFile
|
||||
| FlowFile
|
||||
| ResourceFile
|
||||
| ResourceTypeFile
|
||||
| FolderFile {
|
||||
const typeEnding = getTypeStrFromPath(p);
|
||||
|
||||
if (typeEnding === "folder") {
|
||||
return decoverto.type(FolderFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "script") {
|
||||
return decoverto.type(ScriptFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "variable") {
|
||||
return decoverto.type(VariableFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "flow") {
|
||||
return decoverto.type(FlowFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "resource") {
|
||||
return decoverto.type(ResourceFile).plainToInstance(obj);
|
||||
} else if (typeEnding === "resource-type") {
|
||||
return decoverto.type(ResourceTypeFile).plainToInstance(obj);
|
||||
} else {
|
||||
throw new Error("infer type unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
export function getTypeStrFromPath(
|
||||
p: string,
|
||||
): "script" | "variable" | "flow" | "resource" | "resource-type" | "folder" {
|
||||
const parsed = path.parse(p);
|
||||
if (parsed.ext !== ".json") {
|
||||
throw new Error(
|
||||
"Cannot infer type of non-json file " + JSON.stringify(parsed),
|
||||
);
|
||||
}
|
||||
|
||||
if (parsed.name === "folder.meta") {
|
||||
return "folder";
|
||||
}
|
||||
|
||||
const typeEnding = parsed.name.split(".").at(-1);
|
||||
if (
|
||||
typeEnding === "script" || typeEnding === "variable" ||
|
||||
typeEnding === "flow" || typeEnding === "resource" ||
|
||||
typeEnding === "resource-type"
|
||||
) {
|
||||
return typeEnding;
|
||||
} else {
|
||||
throw new Error("Could not infer type of path " + JSON.stringify(parsed));
|
||||
}
|
||||
}
|
||||
|
||||
180
cli/variable.ts
180
cli/variable.ts
@@ -1,22 +1,7 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
Difference,
|
||||
GlobalOptions,
|
||||
PushDiffs,
|
||||
Resource,
|
||||
setValueByPath,
|
||||
} from "./types.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
EditVariable,
|
||||
ListableVariable,
|
||||
microdiff,
|
||||
Table,
|
||||
VariableService,
|
||||
} from "./deps.ts";
|
||||
import { decoverto, model, property } from "./decoverto.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import { colors, Command, Table, VariableService } from "./deps.ts";
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
@@ -41,100 +26,13 @@ async function list(opts: GlobalOptions) {
|
||||
.render();
|
||||
}
|
||||
|
||||
@model()
|
||||
export class VariableFile implements Resource, PushDiffs {
|
||||
@property(() => String)
|
||||
type VariableFile = {
|
||||
value: string;
|
||||
@property(() => Boolean)
|
||||
is_secret: boolean;
|
||||
@property(() => String)
|
||||
description: string;
|
||||
@property(() => Number)
|
||||
account?: number;
|
||||
@property(() => Boolean)
|
||||
is_oauth?: boolean;
|
||||
|
||||
constructor(value: string, is_secret: boolean, description: string) {
|
||||
this.value = value;
|
||||
this.is_secret = is_secret;
|
||||
this.description = description;
|
||||
}
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (await VariableService.existsVariable({ workspace, path: remotePath })) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing variable...`,
|
||||
),
|
||||
);
|
||||
const changeset: EditVariable = {};
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path.length !== 1 ||
|
||||
!["path", "value", "is_secret", "description"].includes(
|
||||
diff.path[0] as string,
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid variable 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) {
|
||||
console.log(colors.yellow("! Skipping empty changeset"));
|
||||
return;
|
||||
}
|
||||
|
||||
await VariableService.updateVariable({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow("Creating new variable..."));
|
||||
await VariableService.createVariable({
|
||||
workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
description: this.description,
|
||||
is_secret: this.is_secret,
|
||||
value: this.value,
|
||||
account: this.account,
|
||||
is_oauth: this.is_oauth,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: ListableVariable | undefined;
|
||||
try {
|
||||
existing = await VariableService.getVariable({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
@@ -160,10 +58,72 @@ export async function pushVariable(
|
||||
filePath: string,
|
||||
remotePath: string,
|
||||
) {
|
||||
const data = decoverto.type(VariableFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
await data.push(workspace, remotePath);
|
||||
const data: VariableFile = JSON.parse(await Deno.readTextFile(filePath));
|
||||
if (await VariableService.existsVariable({ workspace, path: remotePath })) {
|
||||
const existing = await VariableService.getVariable({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
if (existing.is_oauth != data.is_oauth) {
|
||||
console.log(
|
||||
colors.red.underline.bold(
|
||||
"Remote variable at " +
|
||||
remotePath +
|
||||
" exists & has a different oauth state. This cannot be updated. If you wish to do this anyways, consider deleting the remote resource.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing.account != data.account) {
|
||||
console.log(
|
||||
colors.red.underline.bold(
|
||||
"Remote variable at " +
|
||||
remotePath +
|
||||
" exists & has a different account state. This cannot be updated. If you wish to do this anyways, consider deleting the remote resource.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing.is_secret && !data.is_secret) {
|
||||
console.log(
|
||||
colors.red.underline.bold(
|
||||
"Remote variable at " +
|
||||
remotePath +
|
||||
" exists & is secret. Variables cannot be updated to be no longer secret. If you wish to do this anyways, consider deleting the remote resource.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const actual_secret = data.is_secret ? true : undefined;
|
||||
|
||||
console.log(colors.yellow("Updating existing variable..."));
|
||||
await VariableService.updateVariable({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
requestBody: {
|
||||
description: data.description,
|
||||
is_secret: actual_secret,
|
||||
path: remotePath,
|
||||
value: data.value,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log(colors.yellow("Creating new variable..."));
|
||||
await VariableService.createVariable({
|
||||
workspace,
|
||||
requestBody: {
|
||||
path: remotePath,
|
||||
description: data.description,
|
||||
is_secret: data.is_secret,
|
||||
value: data.value,
|
||||
account: data.account,
|
||||
is_oauth: data.is_oauth,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
|
||||
@@ -11,31 +11,14 @@ import {
|
||||
Table,
|
||||
WorkspaceService,
|
||||
} from "./deps.ts";
|
||||
import { decoverto, model, property } from "./decoverto.ts";
|
||||
import { requireLogin } from "./context.ts";
|
||||
|
||||
@model()
|
||||
export class Workspace {
|
||||
@property(() => String)
|
||||
export type Workspace = {
|
||||
remote: string;
|
||||
@property(() => String)
|
||||
workspaceId: string;
|
||||
@property(() => String)
|
||||
name: string;
|
||||
@property(() => String)
|
||||
token: string;
|
||||
|
||||
constructor(
|
||||
remote: string,
|
||||
workspaceId: string,
|
||||
name: string,
|
||||
token: string,
|
||||
) {
|
||||
this.remote = remote;
|
||||
this.workspaceId = workspaceId;
|
||||
this.name = name;
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function makeWorkspaceStream(
|
||||
readable: ReadableStream<Uint8Array>,
|
||||
@@ -50,9 +33,7 @@ function makeWorkspaceStream(
|
||||
if (line.length <= 2) {
|
||||
return;
|
||||
}
|
||||
const workspace = decoverto.type(Workspace).rawToInstance(line);
|
||||
workspace.remote = new URL(workspace.remote).toString(); // add trailing slash in all cases!
|
||||
controller.enqueue(workspace);
|
||||
controller.enqueue(JSON.parse(line) as Workspace);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
@@ -61,17 +42,14 @@ function makeWorkspaceStream(
|
||||
);
|
||||
}
|
||||
|
||||
export async function getWorkspaceStream() {
|
||||
const file = await Deno.open((await getRootStore()) + "remotes.ndjson", {
|
||||
write: false,
|
||||
read: true,
|
||||
});
|
||||
return makeWorkspaceStream(file.readable);
|
||||
}
|
||||
|
||||
export async function allWorkspaces(): Promise<Workspace[]> {
|
||||
async function allWorkspaces(): Promise<Workspace[]> {
|
||||
try {
|
||||
const workspaceStream = await getWorkspaceStream();
|
||||
const file = await Deno.open((await getRootStore()) + "remotes.ndjson", {
|
||||
write: false,
|
||||
read: true,
|
||||
});
|
||||
const workspaceStream = makeWorkspaceStream(file.readable);
|
||||
|
||||
const workspaces: Workspace[] = [];
|
||||
for await (const workspace of workspaceStream) {
|
||||
workspaces.push(workspace);
|
||||
@@ -109,7 +87,8 @@ export async function getActiveWorkspace(
|
||||
export async function getWorkspaceByName(
|
||||
workspaceName: string,
|
||||
): Promise<Workspace | undefined> {
|
||||
const workspaceStream = await getWorkspaceStream();
|
||||
const file = await Deno.open((await getRootStore()) + "remotes.ndjson");
|
||||
const workspaceStream = makeWorkspaceStream(file.readable);
|
||||
for await (const workspace of workspaceStream) {
|
||||
if (workspace.name === workspaceName) {
|
||||
return workspace;
|
||||
@@ -206,7 +185,6 @@ export async function add(
|
||||
remote = new URL(await Input.prompt("Enter the Remote URL")).toString();
|
||||
}
|
||||
}
|
||||
remote = new URL(remote).toString(); // add trailing slash in all cases!
|
||||
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
while (!token) {
|
||||
@@ -214,10 +192,7 @@ export async function add(
|
||||
}
|
||||
|
||||
if (opts.create) {
|
||||
setClient(
|
||||
token,
|
||||
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
|
||||
);
|
||||
setClient(token, remote.endsWith('/') ? remote.substring(0, remote.length - 1) : remote);
|
||||
|
||||
if (
|
||||
!await WorkspaceService.existsWorkspace({
|
||||
@@ -249,7 +224,6 @@ export async function add(
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -260,7 +234,7 @@ export async function addWorkspace(workspace: Workspace) {
|
||||
file.close();
|
||||
}
|
||||
|
||||
export async function removeWorkspace(name: string) {
|
||||
async function remove(_opts: GlobalOptions, name: string) {
|
||||
const orgWorkspaces = await allWorkspaces();
|
||||
await Deno.writeTextFile(
|
||||
(await getRootStore()) + "remotes.ndjson",
|
||||
@@ -269,10 +243,6 @@ export async function removeWorkspace(name: string) {
|
||||
.map((x) => JSON.stringify(x))
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
async function remove(_opts: GlobalOptions, name: string) {
|
||||
await removeWorkspace(name);
|
||||
console.log(colors.green.underline("Succesfully removed workspace!"));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,41 +20,29 @@ services:
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
windmill_server:
|
||||
windmill:
|
||||
image: ghcr.io/windmill-labs/windmill:main
|
||||
deploy:
|
||||
replicas: 1
|
||||
# Set privileged to true if enabling nsjail
|
||||
privileged: false
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:8000
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
|
||||
- BASE_URL=http://${WM_BASE_URL}
|
||||
- BASE_INTERNAL_URL=http://localhost:8000
|
||||
- RUST_LOG=info
|
||||
## You can set the number of workers to > 0 and not need any separate worker service
|
||||
- NUM_WORKERS=0
|
||||
- DISABLE_SERVER=false
|
||||
- METRICS_ADDR=false
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
windmill_worker:
|
||||
image: ghcr.io/windmill-labs/windmill:main
|
||||
deploy:
|
||||
replicas: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
|
||||
- BASE_URL=http://${WM_BASE_URL}
|
||||
- BASE_INTERNAL_URL=http://windmill_server:8000
|
||||
- RUST_LOG=info
|
||||
- NUM_WORKERS=1
|
||||
- DISABLE_SERVER=true
|
||||
- NUM_WORKERS=3
|
||||
- KEEP_JOB_DIR=false
|
||||
- DENO_PATH=/usr/bin/deno
|
||||
- PYTHON_PATH=/usr/local/bin/python3
|
||||
- METRICS_ADDR=false
|
||||
# for ease of use, nsjail which provide isolation in untrusted environment is disabled by default.
|
||||
# To enable it, uncomment the following line, set the container as privileged and
|
||||
# rebuild the image with nsjail=true or use the enterprise edition
|
||||
# - DISABLE_NSJAIL=false
|
||||
# - DISABLE_NUSER=false
|
||||
# - NSJAIL_PATH=nsjail
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
200
frontend/package-lock.json
generated
200
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.63.1",
|
||||
"version": "1.61.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.63.1",
|
||||
"version": "1.61.1",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
@@ -56,25 +56,25 @@
|
||||
"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-preprocess": "^5.0.0",
|
||||
"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"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55.1"
|
||||
"svelte": "^3.53.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||
@@ -848,6 +848,20 @@
|
||||
"svelte": "^3.44.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/package/node_modules/svelte2tsx": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
|
||||
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/vite-plugin-svelte": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.2.tgz",
|
||||
@@ -5818,18 +5832,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sorcery": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
|
||||
"integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
|
||||
"integrity": "sha512-R5ocFmKZQFfSTstfOtHjJuAwbpGyf9qjQa1egyhvXSbM7emjrtLXtGdZsDJDABC85YBfVvrOiGWKSYXPKdvP1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
"buffer-crc32": "^0.2.5",
|
||||
"minimist": "^1.2.0",
|
||||
"sander": "^0.5.0"
|
||||
"sander": "^0.5.0",
|
||||
"sourcemap-codec": "^1.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"sorcery": "bin/sorcery"
|
||||
"sorcery": "bin/index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
@@ -5850,6 +5864,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-correct": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
|
||||
@@ -6121,9 +6141,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "3.55.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz",
|
||||
"integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==",
|
||||
"version": "3.55.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz",
|
||||
"integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
@@ -6176,6 +6196,15 @@
|
||||
"svelte": "^3.55.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-dnd-action": {
|
||||
"version": "0.9.22",
|
||||
"resolved": "https://registry.npmjs.org/svelte-dnd-action/-/svelte-dnd-action-0.9.22.tgz",
|
||||
"integrity": "sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"svelte": ">=3.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-floating-ui": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.1.6.tgz",
|
||||
@@ -6185,25 +6214,18 @@
|
||||
"svelte2tsx": "^0.5.11"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-floating-ui/node_modules/svelte2tsx": {
|
||||
"version": "0.5.23",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.5.23.tgz",
|
||||
"integrity": "sha512-jYFnugTQRFmUpvLXPQrKzVYcW5ErT+0QCxg027Zx9BuvYefMZFuoBSTDYe7viPEFGrPPiLgT2m7f5n9khE7f7Q==",
|
||||
"dependencies": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.24",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-grid": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-grid/-/svelte-grid-5.1.1.tgz",
|
||||
"integrity": "sha512-PJTC4PUejb8RzcUjymWnf1HALqseUBwxl+p+i5uJlDIC1+zXKok1J16IDoHQxXhNj+INcF9KHE7hIm++NurSAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-heros": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-heros/-/svelte-heros-2.3.5.tgz",
|
||||
"integrity": "sha512-08PdccaeRPP1pVa90AGieTwGzrNtXpC1Fry+i95OTvcR3xbGRU/hxK4rnaFYvGgk1Pxj9YT6GKGTEX8uXE9XJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-highlight": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-6.2.1.tgz",
|
||||
@@ -6256,9 +6278,9 @@
|
||||
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
|
||||
},
|
||||
"node_modules/svelte-preprocess": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz",
|
||||
"integrity": "sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
|
||||
"integrity": "sha512-q7lpa7i2FBu8Pa+G0MmuQQWETBwCKgsGmuq1Sf6n8q4uaG9ZLcLP0Y+etC6bF4sE6EbLxfiI38zV6RfPe3RSfg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
@@ -6266,7 +6288,7 @@
|
||||
"@types/sass": "^1.43.1",
|
||||
"detect-indent": "^6.1.0",
|
||||
"magic-string": "^0.27.0",
|
||||
"sorcery": "^0.11.0",
|
||||
"sorcery": "^0.10.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6318,12 +6340,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-range-slider-pips": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-range-slider-pips/-/svelte-range-slider-pips-2.1.1.tgz",
|
||||
"integrity": "sha512-fLkhfnyEc4uMOCZCl8IPyC0e9lujrjqdSyrKw28Y8j99YUPZR+pHnSxde7PqhM0ST6eyO4ugh2nFnLZTIoG9aQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-select": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-5.0.2.tgz",
|
||||
@@ -6377,17 +6393,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte2tsx": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.1.tgz",
|
||||
"integrity": "sha512-O/1+5UyChfmhp1/GUv8b8iveTrn6eZwHxEXc+rw7LMKRidr9KHk5w/EiliLjDUwHa2VA6CoEty+CQylROVU4Sw==",
|
||||
"dev": true,
|
||||
"version": "0.5.22",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.5.22.tgz",
|
||||
"integrity": "sha512-OytIql7Bv53oFuL0jjsnp/gNvR4ngAUdAjswgibmIQT2Lj2OIQYn2J3gKqRd+wSj/n3M/wrz4zJpudQRSfncZw==",
|
||||
"dependencies": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55",
|
||||
"typescript": "^4.9.4"
|
||||
"svelte": "^3.24",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-tags": {
|
||||
@@ -7163,12 +7178,6 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yootils": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yootils/-/yootils-0.3.1.tgz",
|
||||
"integrity": "sha512-A7AMeJfGefk317I/3tBoUYRcDcNavKEkpiPN/nQsBz/viI2GvT7BtrqdPD6rGqBFN8Ax7v4obf+Cl32JF9DDVw==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -7639,6 +7648,18 @@
|
||||
"kleur": "^4.1.5",
|
||||
"sade": "^1.8.1",
|
||||
"svelte2tsx": "~0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte2tsx": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.0.tgz",
|
||||
"integrity": "sha512-TrxfQkO7CKi8Pu2eC/FyteDCdk3OOeQV5u6z7OjYAsOhsd0ClzAKqxJdvp6xxNQLrbFzf/XvCi9Fy8MQ1MleFA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sveltejs/vite-plugin-svelte": {
|
||||
@@ -11210,15 +11231,15 @@
|
||||
}
|
||||
},
|
||||
"sorcery": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
|
||||
"integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
|
||||
"integrity": "sha512-R5ocFmKZQFfSTstfOtHjJuAwbpGyf9qjQa1egyhvXSbM7emjrtLXtGdZsDJDABC85YBfVvrOiGWKSYXPKdvP1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
"buffer-crc32": "^0.2.5",
|
||||
"minimist": "^1.2.0",
|
||||
"sander": "^0.5.0"
|
||||
"sander": "^0.5.0",
|
||||
"sourcemap-codec": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
@@ -11233,6 +11254,12 @@
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"spdx-correct": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
|
||||
@@ -11448,9 +11475,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.55.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz",
|
||||
"integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ=="
|
||||
"version": "3.55.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz",
|
||||
"integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA=="
|
||||
},
|
||||
"svelte-autosize": {
|
||||
"version": "1.0.1",
|
||||
@@ -11489,6 +11516,13 @@
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
},
|
||||
"svelte-dnd-action": {
|
||||
"version": "0.9.22",
|
||||
"resolved": "https://registry.npmjs.org/svelte-dnd-action/-/svelte-dnd-action-0.9.22.tgz",
|
||||
"integrity": "sha512-lOQJsNLM1QWv5mdxIkCVtk6k4lHCtLgfE59y8rs7iOM6erchbLC9hMEFYSveZz7biJV0mpg7yDSs4bj/RT/YkA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"svelte-floating-ui": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.1.6.tgz",
|
||||
@@ -11496,17 +11530,6 @@
|
||||
"requires": {
|
||||
"@floating-ui/dom": "^1.0.4",
|
||||
"svelte2tsx": "^0.5.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte2tsx": {
|
||||
"version": "0.5.23",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.5.23.tgz",
|
||||
"integrity": "sha512-jYFnugTQRFmUpvLXPQrKzVYcW5ErT+0QCxg027Zx9BuvYefMZFuoBSTDYe7viPEFGrPPiLgT2m7f5n9khE7f7Q==",
|
||||
"requires": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"svelte-grid": {
|
||||
@@ -11515,6 +11538,12 @@
|
||||
"integrity": "sha512-PJTC4PUejb8RzcUjymWnf1HALqseUBwxl+p+i5uJlDIC1+zXKok1J16IDoHQxXhNj+INcF9KHE7hIm++NurSAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-heros": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-heros/-/svelte-heros-2.3.5.tgz",
|
||||
"integrity": "sha512-08PdccaeRPP1pVa90AGieTwGzrNtXpC1Fry+i95OTvcR3xbGRU/hxK4rnaFYvGgk1Pxj9YT6GKGTEX8uXE9XJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-highlight": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-6.2.1.tgz",
|
||||
@@ -11558,25 +11587,19 @@
|
||||
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
|
||||
},
|
||||
"svelte-preprocess": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz",
|
||||
"integrity": "sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
|
||||
"integrity": "sha512-q7lpa7i2FBu8Pa+G0MmuQQWETBwCKgsGmuq1Sf6n8q4uaG9ZLcLP0Y+etC6bF4sE6EbLxfiI38zV6RfPe3RSfg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/pug": "^2.0.6",
|
||||
"@types/sass": "^1.43.1",
|
||||
"detect-indent": "^6.1.0",
|
||||
"magic-string": "^0.27.0",
|
||||
"sorcery": "^0.11.0",
|
||||
"sorcery": "^0.10.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"svelte-range-slider-pips": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte-range-slider-pips/-/svelte-range-slider-pips-2.1.1.tgz",
|
||||
"integrity": "sha512-fLkhfnyEc4uMOCZCl8IPyC0e9lujrjqdSyrKw28Y8j99YUPZR+pHnSxde7PqhM0ST6eyO4ugh2nFnLZTIoG9aQ==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-select": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-5.0.2.tgz",
|
||||
@@ -11618,10 +11641,9 @@
|
||||
}
|
||||
},
|
||||
"svelte2tsx": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.6.1.tgz",
|
||||
"integrity": "sha512-O/1+5UyChfmhp1/GUv8b8iveTrn6eZwHxEXc+rw7LMKRidr9KHk5w/EiliLjDUwHa2VA6CoEty+CQylROVU4Sw==",
|
||||
"dev": true,
|
||||
"version": "0.5.22",
|
||||
"resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.5.22.tgz",
|
||||
"integrity": "sha512-OytIql7Bv53oFuL0jjsnp/gNvR4ngAUdAjswgibmIQT2Lj2OIQYn2J3gKqRd+wSj/n3M/wrz4zJpudQRSfncZw==",
|
||||
"requires": {
|
||||
"dedent-js": "^1.0.1",
|
||||
"pascal-case": "^3.1.1"
|
||||
@@ -12161,12 +12183,6 @@
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"yootils": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yootils/-/yootils-0.3.1.tgz",
|
||||
"integrity": "sha512-A7AMeJfGefk317I/3tBoUYRcDcNavKEkpiPN/nQsBz/viI2GvT7BtrqdPD6rGqBFN8Ax7v4obf+Cl32JF9DDVw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.63.1",
|
||||
"version": "1.61.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -42,22 +42,22 @@
|
||||
"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-preprocess": "^5.0.0",
|
||||
"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": {
|
||||
@@ -82,6 +82,6 @@
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55.1"
|
||||
"svelte": "^3.53.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
<FieldHeader {label} {required} {type} {contentEncoding} {format} {itemsType} />
|
||||
{/if}
|
||||
{#if editableSchema}
|
||||
<div class="p-2 my-1 text-xs border-solid border border-gray-400">
|
||||
<div class="my-1 text-xs border-solid border border-gray-400 rounded p-2">
|
||||
<span
|
||||
class="underline"
|
||||
on:click={() => {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { userWorkspaces, workspaceStore } from '$lib/stores'
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { slide } from 'svelte/transition'
|
||||
import InlineCodeCopy from './InlineCodeCopy.svelte'
|
||||
|
||||
$: opened = false
|
||||
$: workspace = $userWorkspaces.find((e) => e.id === $workspaceStore)
|
||||
$: workspaceName = workspace?.name
|
||||
$: workspaceId = workspace?.id
|
||||
$: url = `${$page.url.protocol}//${$page.url.hostname}/`
|
||||
</script>
|
||||
|
||||
<div class="text-sm mt-2 flex">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="underline mr-4"
|
||||
on:click={() => {
|
||||
opened = !opened
|
||||
}}
|
||||
>
|
||||
CLI quick setup
|
||||
<Icon class="ml-2" data={opened ? faChevronUp : faChevronDown} scale={0.7} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if opened}
|
||||
<div
|
||||
transition:slide|local
|
||||
class="bg-gray-100 border-l-4 border-gray-600 text-gray-700 p-4 m-4"
|
||||
role="alert"
|
||||
id="dynamic-input-help-box"
|
||||
>
|
||||
<ul class="pl-0 list-decimal list-inside">
|
||||
<li
|
||||
>Install the latest wmill CLI from deno.land: <InlineCodeCopy
|
||||
content={'deno install --unstable -A https://deno.land/x/wmill/main.ts'}
|
||||
/></li
|
||||
>
|
||||
<li
|
||||
>Setup the wmill cli for this workspace & remote: <InlineCodeCopy
|
||||
content={`wmill workspace add ${workspaceName} ${workspaceId} ${url}`}
|
||||
/></li
|
||||
>
|
||||
<li>Follow the prompts in your terminal</li>
|
||||
<li>Use the run command above!</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -20,8 +20,6 @@
|
||||
| 'gif'
|
||||
| 'error'
|
||||
| 'approval'
|
||||
| 'svg'
|
||||
| 'filename'
|
||||
| undefined
|
||||
|
||||
$: resultKind = inferResultKind(result)
|
||||
@@ -63,16 +61,12 @@
|
||||
return 'html'
|
||||
} else if (keys.length == 1 && keys[0] == 'png') {
|
||||
return 'png'
|
||||
} else if (keys.length == 1 && keys[0] == 'svg') {
|
||||
return 'svg'
|
||||
} else if (keys.length == 1 && keys[0] == 'jpeg') {
|
||||
return 'jpeg'
|
||||
} else if (keys.length == 1 && keys[0] == 'file') {
|
||||
return 'file'
|
||||
} else if (keys.length == 1 && keys[0] == 'error') {
|
||||
return 'error'
|
||||
} else if (keys.length === 2 && keys.includes('file') && keys.includes('filename')) {
|
||||
return 'filename'
|
||||
} else if (
|
||||
keys.length == 3 &&
|
||||
keys.includes('resume') &&
|
||||
@@ -143,16 +137,16 @@
|
||||
</div>
|
||||
<p
|
||||
class="text-gray-600 mb-2 text-left border-2 !border-t-0 rounded-b border-red-400 overflow-auto p-1"
|
||||
>Rendering HTML can expose you to <a
|
||||
href="https://owasp.org/www-community/attacks/xss/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="hover:underline">XSS attacks</a
|
||||
>. Only enable it if you trust the author of the script.
|
||||
>Rendering HTML can expose you to <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noreferrer" class="hover:underline">XSS attacks</a>.
|
||||
Only enable it if you trust the author of the script.
|
||||
</p>
|
||||
</div>
|
||||
<div class="center-center">
|
||||
<Button size="sm" color="dark" on:click={() => (enableHtml = true)}>
|
||||
<Button
|
||||
size="sm"
|
||||
color="dark"
|
||||
on:click={() => enableHtml = true}
|
||||
>
|
||||
Enable HTML rendering
|
||||
</Button>
|
||||
</div>
|
||||
@@ -171,11 +165,6 @@
|
||||
src="data:image/jpeg;base64,{result.jpeg}"
|
||||
/>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'svg'}
|
||||
<div
|
||||
><a download="windmill.svg" href="data:text/plain;base64,{btoa(result.svg)}">Download</a>
|
||||
</div>
|
||||
<div class="h-full overflow-auto">{@html result.svg} </div>
|
||||
{:else if !forceJson && resultKind == 'gif'}
|
||||
<div class="h-full"
|
||||
><img alt="gif rendered" class="w-auto h-full" src="data:image/gif;base64,{result.gif}" />
|
||||
@@ -186,12 +175,6 @@
|
||||
>Download</a
|
||||
>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind === 'filename'}
|
||||
<div>
|
||||
<a download={result.filename} href="data:application/octet-stream;base64,{result.file}">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
{:else if !forceJson && resultKind == 'error'}<div>
|
||||
<span class="text-red-500 font-semibold text-sm whitespace-pre-wrap"
|
||||
>{result.error.name}: {result.error.message}</span
|
||||
|
||||
@@ -298,9 +298,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
const wsProtocol = $page.url.protocol == 'https:' ? 'wss' : 'ws'
|
||||
if (lang == 'typescript') {
|
||||
await connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/deno`, 'deno', {
|
||||
await connectToLanguageServer(`wss://${$page.url.host}/ws/deno`, 'deno', {
|
||||
certificateStores: null,
|
||||
enablePaths: [],
|
||||
config: null,
|
||||
@@ -331,7 +330,7 @@
|
||||
}
|
||||
})
|
||||
} else if (lang === 'python') {
|
||||
await connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/pyright`, 'pyright', {
|
||||
await connectToLanguageServer(`wss://${$page.url.host}/ws/pyright`, 'pyright', {
|
||||
executionEnvironments: [
|
||||
{
|
||||
root: '/tmp/pyright',
|
||||
@@ -342,7 +341,7 @@
|
||||
]
|
||||
})
|
||||
|
||||
connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/black`, 'black', {
|
||||
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
|
||||
formatters: {
|
||||
black: {
|
||||
command: 'black',
|
||||
@@ -354,7 +353,7 @@
|
||||
}
|
||||
})
|
||||
} else if (lang === 'go') {
|
||||
connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/go`, 'go', {
|
||||
connectToLanguageServer(`wss://${$page.url.host}/ws/go`, 'go', {
|
||||
'build.allowImplicitNetworkAccess': true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
import PickHubScript from './flows/pickers/PickHubScript.svelte'
|
||||
import ToggleHubWorkspace from './ToggleHubWorkspace.svelte'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
import Popover from './Popover.svelte'
|
||||
import Kbd from './common/kbd/Kbd.svelte'
|
||||
|
||||
export let lang: 'python3' | 'deno' | 'go' | 'bash'
|
||||
export let editor: Editor
|
||||
@@ -227,137 +225,121 @@
|
||||
<ResourceEditor bind:this={resourceEditor} on:refresh={resourcePicker.openDrawer} />
|
||||
<VariableEditor bind:this={variableEditor} on:create={variablePicker.openDrawer} />
|
||||
|
||||
<div class="flex justify-between items-center overflow-y-auto w-full p-1">
|
||||
<div class="flex items-center">
|
||||
<Badge color={validCode ? 'green' : 'red'} class="min-w-[60px] mr-3">
|
||||
{validCode ? 'Valid' : 'Invalid'}
|
||||
</Badge>
|
||||
<div class="flex items-center divide-x">
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="pr-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
on:click={contextualVariablePicker.openDrawer}
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faDollarSign }}
|
||||
{iconOnly}
|
||||
>
|
||||
+Context Var
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add context variable
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
on:click={variablePicker.openDrawer}
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faDollarSign }}
|
||||
{iconOnly}
|
||||
>
|
||||
+Variable
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add variable
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={resourcePicker.openDrawer}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faCube }}
|
||||
>
|
||||
+Resource
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add resource
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.clearContent}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faRotateLeft }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reset
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.reloadWebsocket}
|
||||
startIcon={{ icon: faRotate }}
|
||||
>
|
||||
{#if !iconOnly}
|
||||
Assistant
|
||||
<div class="flex flex-row justify-between items-center overflow-hidden w-full px-1">
|
||||
<div class="flex flex-row divide-x items-center">
|
||||
<div class="mx-2">
|
||||
{#if validCode}
|
||||
<Badge color="green">Inputs</Badge>
|
||||
{:else}
|
||||
<Badge color="red">Inputs</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="mr-1 !font-medium"
|
||||
on:click={contextualVariablePicker.openDrawer}
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faDollarSign }}
|
||||
{iconOnly}
|
||||
>
|
||||
+Context Var
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="mx-1 !font-medium"
|
||||
on:click={variablePicker.openDrawer}
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faDollarSign }}
|
||||
{iconOnly}
|
||||
>
|
||||
+Variable
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
btnClasses="mx-1 !font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={resourcePicker.openDrawer}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faCube }}
|
||||
>
|
||||
+Resource
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
btnClasses="mx-1 !font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.clearContent}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faRotateLeft }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.reloadWebsocket}
|
||||
startIcon={{ icon: faRotate }}
|
||||
>
|
||||
{#if !iconOnly}
|
||||
Assistant
|
||||
{/if}
|
||||
<span class="ml-1 -my-1">
|
||||
{#if lang == 'deno'}
|
||||
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
|
||||
{:else if lang == 'go'}
|
||||
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
|
||||
{:else if lang == 'python3'}
|
||||
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
|
||||
<span class={websocketAlive.black ? 'green' : 'text-red-700'}>Black</span>)
|
||||
{/if}
|
||||
<span class="ml-1 -my-1">
|
||||
{#if lang == 'deno'}
|
||||
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
|
||||
{:else if lang == 'go'}
|
||||
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
|
||||
{:else if lang == 'python3'}
|
||||
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
|
||||
<span class={websocketAlive.black ? 'green' : 'text-red-700'}>Black</span>)
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reload assistant
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.format}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faBroom }}
|
||||
>
|
||||
</span>
|
||||
</Button></div
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.format}
|
||||
startIcon={{ icon: faBroom }}
|
||||
>
|
||||
{#if !iconOnly}
|
||||
Format (Ctrl+S)
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Format <Kbd class="!text-gray-800">Ctrl</Kbd> + <Kbd class="!text-gray-800">S</Kbd>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</Button></div
|
||||
>
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<div>
|
||||
<Button
|
||||
btnClasses="mx-1 !font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={scriptPicker.openDrawer}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Script
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={scriptPicker.openDrawer}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faEye }}
|
||||
>
|
||||
Script
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Script
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -112,8 +112,8 @@
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
}
|
||||
sendUserToast(`Flow saved at ${$flowStore.path}`)
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
sendUserToast(`Success! flow saved at ${$flowStore.path}`)
|
||||
goto(`/flows/get/${$flowStore.path}`)
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
import type { Job } from '$lib/gen'
|
||||
import JobStatus from '$lib/components/JobStatus.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { displayDate, displayDaysAgo } from '$lib/utils'
|
||||
import { displayDaysAgo } from '$lib/utils'
|
||||
import {
|
||||
faCalendar,
|
||||
faClock,
|
||||
faRobot,
|
||||
faScroll,
|
||||
faUser,
|
||||
faBarsStaggered,
|
||||
faMemory
|
||||
faBarsStaggered
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import ScheduleEditor from './ScheduleEditor.svelte'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
@@ -40,31 +39,16 @@
|
||||
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span
|
||||
class="mx-2 text-2xs text-gray-600"
|
||||
>
|
||||
{#if job['success'] != undefined}
|
||||
Received job: {displayDate(job.created_at ?? '')}
|
||||
{:else}
|
||||
{#key time}
|
||||
Received job {displayDaysAgo(job.created_at ?? '')}
|
||||
{/key}
|
||||
{/if}</span
|
||||
{#key time}
|
||||
Received job {displayDaysAgo(job.created_at ?? '')}
|
||||
{/key}</span
|
||||
>
|
||||
</div>
|
||||
{#if job && 'started_at' in job && job.started_at}
|
||||
<div>
|
||||
<Icon class="text-gray-700" data={faClock} scale={SMALL_ICON_SCALE} /><span class="mx-2">
|
||||
{#if job['success'] != undefined}
|
||||
Started: {displayDate(job.started_at ?? '')}
|
||||
{:else}
|
||||
{#key time}
|
||||
Started {displayDaysAgo(job.started_at ?? '')}{/key}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if job && job['mem_peak']}
|
||||
<div>
|
||||
<Icon class="text-gray-700" data={faMemory} scale={SMALL_ICON_SCALE} /><span class="mx-2">
|
||||
Mem peak: {(job['mem_peak'] / 1024).toPrecision(5)}MB</span
|
||||
{#key time}
|
||||
Started {displayDaysAgo(job.started_at ?? '')}{/key}</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -164,14 +164,6 @@
|
||||
let selected: 'graph' | 'sequence' = 'graph'
|
||||
|
||||
let payload: string = '"a test payload in json"'
|
||||
|
||||
function isSuccess(arg: any): boolean | undefined {
|
||||
if (arg == undefined) {
|
||||
return undefined
|
||||
} else {
|
||||
return arg == true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if job}
|
||||
@@ -445,7 +437,6 @@
|
||||
</div>
|
||||
|
||||
<FlowGraph
|
||||
success={isSuccess(job?.['success'])}
|
||||
flowModuleStates={localFlowModuleStates}
|
||||
on:click={(e) => {
|
||||
if (e.detail.id) {
|
||||
|
||||
@@ -26,10 +26,4 @@
|
||||
$: lang = getLang(language)
|
||||
</script>
|
||||
|
||||
{#if code?.length < 5000}
|
||||
<Highlight className="nowrap {$$props.class}" language={lang} {code} />
|
||||
{:else}
|
||||
<pre class="overflow-auto max-h-screen {$$props.class}"
|
||||
><code class="language-{language}">{code}</code></pre
|
||||
>
|
||||
{/if}
|
||||
<Highlight className="nowrap {$$props.class}" language={lang} {code} />
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { copyToClipboard } from '$lib/utils'
|
||||
import { faClipboard } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
|
||||
export let content: string
|
||||
</script>
|
||||
|
||||
<pre class="bg-gray-700 text-gray-100 p-2 font-mono text-sm whitespace-pre-wrap"
|
||||
>{content} <span on:click={() => copyToClipboard(content)} class="cursor-pointer ml-2"
|
||||
><Icon data={faClipboard} /></span
|
||||
></pre
|
||||
>
|
||||
@@ -165,7 +165,7 @@
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate
|
||||
? `\`${arg?.value?.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
? `\`${arg?.value.toString().replaceAll('`', '\\`') ?? ''}\``
|
||||
: arg.value
|
||||
? JSON.stringify(arg?.value, null, 4)
|
||||
: ''
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
export let content: string | undefined
|
||||
export let isLoading: boolean
|
||||
export let duration: number | undefined = undefined
|
||||
export let mem: number | undefined = undefined
|
||||
|
||||
let scroll = true
|
||||
let div: HTMLElement | null = null
|
||||
@@ -51,11 +50,6 @@
|
||||
{:else if duration}
|
||||
<span class="absolute text-xs text-gray-500 top-2 left-2">took {duration}ms</span>
|
||||
{/if}
|
||||
{#if mem}
|
||||
<span class="absolute text-xs text-gray-500 top-2 left-36"
|
||||
>mem peak: {(mem / 1024).toPrecision(4)}MB</span
|
||||
>
|
||||
{/if}
|
||||
<pre class="whitespace-pre-wrap break-words bg-gray-50 text-xs w-full p-2"
|
||||
>{#if content}<span>{content}</span>{:else if !isLoading}<span class="text-gray-600"
|
||||
>No logs are available yet</span
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<input
|
||||
type="text"
|
||||
bind:value={summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
placeholder="A short summary displayed when it is listed"
|
||||
disabled={!own}
|
||||
/>
|
||||
|
||||
|
||||
@@ -34,11 +34,7 @@
|
||||
}
|
||||
|
||||
function resourceToValue() {
|
||||
if (path) {
|
||||
value = `$res:${path}`
|
||||
} else {
|
||||
value = undefined
|
||||
}
|
||||
value = `$res:${path}`
|
||||
}
|
||||
|
||||
function isResource() {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
export let placement: PopoverPlacement = 'auto'
|
||||
export let notClickable = false
|
||||
export let popupClass = ''
|
||||
export let disablePopup = false
|
||||
export let disapperTimoout = 100
|
||||
|
||||
const [popperRef, popperContent] = createPopperActions({ placement })
|
||||
@@ -21,12 +20,7 @@
|
||||
const extraOpts = {
|
||||
modifiers: [
|
||||
betterPreventOverflow({ padding: 10 }),
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [8, 8]
|
||||
}
|
||||
},
|
||||
{ name: 'offset', options: { offset: [8, 8] } },
|
||||
{
|
||||
name: 'arrow',
|
||||
options: {
|
||||
@@ -68,7 +62,7 @@
|
||||
<slot />
|
||||
</button>
|
||||
{/if}
|
||||
{#if showTooltip && !disablePopup}
|
||||
{#if showTooltip}
|
||||
<div
|
||||
use:popperContent={extraOpts}
|
||||
on:mouseenter={open}
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import CliHelpBox from './CliHelpBox.svelte'
|
||||
import InlineCodeCopy from './InlineCodeCopy.svelte'
|
||||
|
||||
export let runnable:
|
||||
| {
|
||||
@@ -48,7 +46,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)
|
||||
|
||||
@@ -58,15 +55,10 @@
|
||||
|
||||
export let isValid = true
|
||||
|
||||
// Run later
|
||||
let viewOptions = false
|
||||
let viewCliOptions = false
|
||||
|
||||
let scheduledForStr: string | undefined
|
||||
let invisible_to_owner: false
|
||||
|
||||
$: cliCommand = `wmill ${runnable?.kind} run ${runnable?.path} ${Object.entries(args)
|
||||
.map(([k, v]) => `-i ${k}=${JSON.stringify(v)}`)
|
||||
.join(' ')}`
|
||||
</script>
|
||||
|
||||
<div class="max-w-6xl">
|
||||
@@ -179,7 +171,7 @@
|
||||
<div class="flex items-center gap-1">
|
||||
<Toggle
|
||||
options={{
|
||||
right: `make run invisible to others`
|
||||
right: `run only visible to you`
|
||||
}}
|
||||
bind:checked={invisible_to_owner}
|
||||
/>
|
||||
@@ -208,22 +200,4 @@
|
||||
{buttonText}
|
||||
</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}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Tooltip>
|
||||
Arguments can be edited either using the wizard, or by editing their JSON Schema,
|
||||
<a href="https://docs.windmill.dev/docs/reference/#script-parameters-to-json-schema">see docs</a>
|
||||
Arguments can be edited either using the wizard, or by editing their json-schema
|
||||
<a href="https://docs.windmill.dev/docs/reference/script_arguments_reference">docs</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
if (!keys.includes(key)) {
|
||||
delete args[key]
|
||||
delete inputCheck[key]
|
||||
console.log('DELETED', key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { Button, Kbd } from './common'
|
||||
import { Button } 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'
|
||||
@@ -83,9 +83,8 @@
|
||||
kind: script.kind
|
||||
}
|
||||
})
|
||||
sendUserToast(`New script created at hash ${newHash}`)
|
||||
await goto(`/scripts/edit/${newHash}?step=2`)
|
||||
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
|
||||
sendUserToast(`Success! New script version created with hash ${newHash}`)
|
||||
goto(`/scripts/get/${newHash}`)
|
||||
} catch (error) {
|
||||
sendUserToast(`Impossible to save the script: ${error.body}`, true)
|
||||
}
|
||||
@@ -104,18 +103,9 @@
|
||||
}
|
||||
goto(`?step=${step}`)
|
||||
}
|
||||
|
||||
$: kind = script.kind as 'script' | 'trigger' | 'approval' | undefined
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (event.key == 'Enter' && step == 1) {
|
||||
changeStep(2)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<UnsavedConfirmationModal />
|
||||
<div class="flex flex-col h-screen">
|
||||
@@ -177,11 +167,11 @@
|
||||
<Button
|
||||
size="sm"
|
||||
variant={step == 1 ? 'contained' : 'border'}
|
||||
btnClasses={step == 3 ? 'invisible' : 'inline-flex gap-2'}
|
||||
btnClasses={step == 3 ? 'invisible' : ''}
|
||||
disabled={step === 1 && pathError !== ''}
|
||||
on:click={() => changeStep(step + 1)}
|
||||
>
|
||||
Next {#if step == 1}<Kbd>Enter</Kbd>{/if}
|
||||
Next
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -198,7 +188,7 @@
|
||||
<!-- metadata -->
|
||||
{#if step === 1}
|
||||
<CenteredPage>
|
||||
<h2 class="border-b pb-1 mt-8 mb-2">Path</h2>
|
||||
<h2 class="border-b pb-1 mt-4 mb-2">Path</h2>
|
||||
<Path
|
||||
bind:this={pathC}
|
||||
bind:error={pathError}
|
||||
@@ -208,14 +198,14 @@
|
||||
namePlaceholder="my_script"
|
||||
kind="script"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
|
||||
<h2 class="border-b pb-1 mt-8 mb-4">Summary</h2>
|
||||
<input
|
||||
type="text"
|
||||
bind:this={summaryC}
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
placeholder="A short summary of the script displayed when the script is listed"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-12 mb-6">Language</h2>
|
||||
<h2 class="border-b pb-1 mt-8 mb-6">Language</h2>
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
@@ -230,7 +220,7 @@
|
||||
initContent(lang, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon {lang} /><span class="ml-2 py-4">{label}</span>
|
||||
<LanguageIcon {lang} /><span class="ml-2">{label}</span>
|
||||
</Button>
|
||||
{/each}
|
||||
<Button
|
||||
@@ -244,7 +234,7 @@
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2">PostgreSQL</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -257,10 +247,11 @@
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2">MySQL</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-16 mb-4">
|
||||
<h2 class="border-b pb-1 mt-8 mb-4">Advanced</h2>
|
||||
<div class="mb-4">
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
@@ -310,12 +301,12 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- <div class="ml-3">
|
||||
<div class="ml-3">
|
||||
<Toggle
|
||||
bind:checked={script.is_template}
|
||||
options={{ right: 'Save as a workspace template' }}
|
||||
/>
|
||||
</div> -->
|
||||
</div>
|
||||
</CenteredPage>
|
||||
{:else if step === 2}
|
||||
<ScriptEditor
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
['None', 'none'],
|
||||
['File (base64)', 'base64'],
|
||||
['Enum', 'enum'],
|
||||
['Resource Path', 'resource'],
|
||||
['Format', 'format'],
|
||||
['Pattern', 'pattern']
|
||||
]}
|
||||
|
||||
@@ -109,10 +109,12 @@
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
console.debug('cancelled')
|
||||
}
|
||||
|
||||
export async function clearCurrentJob() {
|
||||
if (currentId) {
|
||||
console.debug('clear')
|
||||
job = undefined
|
||||
await cancelJob()
|
||||
}
|
||||
@@ -146,9 +148,6 @@
|
||||
if (previewJobUpdates.new_logs) {
|
||||
job.logs = (job?.logs ?? '').concat(previewJobUpdates.new_logs)
|
||||
}
|
||||
if (previewJobUpdates.mem_peak) {
|
||||
job.mem_peak = previewJobUpdates.mem_peak
|
||||
}
|
||||
if ((previewJobUpdates.running ?? false) || (previewJobUpdates.completed ?? false)) {
|
||||
job = await JobService.getJob({ workspace: workspace!, id })
|
||||
}
|
||||
@@ -181,6 +180,7 @@
|
||||
|
||||
async function syncer(id: string): Promise<void> {
|
||||
if (currentId != id) {
|
||||
console.debug('stop')
|
||||
return
|
||||
}
|
||||
syncIteration++
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let options: {
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
>
|
||||
<div>
|
||||
Added token: <button on:click={() => copyToClipboard(newToken ?? '')} class="inline"
|
||||
>{newToken} <Icon data={faClipboard} />
|
||||
>{truncate(newToken ?? '', 10, '****')} <Icon data={faClipboard} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="pt-1 text-xs ml-2">
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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
|
||||
@@ -15,7 +14,7 @@
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap bind:result bind:componentInput {id}>
|
||||
<div class="w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm">
|
||||
Results
|
||||
</div>
|
||||
@@ -18,7 +18,6 @@
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
export let preclickAction: (() => Promise<void>) | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -29,8 +28,6 @@
|
||||
let size: ButtonType.Size
|
||||
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
|
||||
@@ -61,7 +58,6 @@
|
||||
</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
|
||||
@@ -71,7 +67,6 @@
|
||||
bind:value={disabled}
|
||||
bind:error={errors.disabled}
|
||||
/>
|
||||
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
|
||||
|
||||
<RunnableWrapper
|
||||
flexWrap
|
||||
@@ -80,23 +75,18 @@
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
{goto}
|
||||
>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
{#if errorsMessage}
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
<Button
|
||||
btnClasses={fillContainer ? 'w-full h-full' : ''}
|
||||
{disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={async (e) => {
|
||||
if (preclickAction) {
|
||||
await preclickAction()
|
||||
}
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
ownClick = true
|
||||
@@ -112,9 +102,7 @@
|
||||
{color}
|
||||
{loading}
|
||||
>
|
||||
<span class="truncate">
|
||||
{labelValue}
|
||||
</span>
|
||||
{labelValue}
|
||||
</Button>
|
||||
</AlignWrapper>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Bar, Line } from 'svelte-chartjs'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -11,6 +12,7 @@
|
||||
CategoryScale,
|
||||
BarElement
|
||||
} from 'chart.js'
|
||||
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
@@ -18,7 +20,6 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -70,7 +71,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.line} bind:value={lineChart} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
{#if lineChart}
|
||||
<Line {data} {options} />
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -21,16 +20,14 @@
|
||||
bind:clientHeight={h}
|
||||
bind:clientWidth={w}
|
||||
>
|
||||
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:result>
|
||||
{#key result}
|
||||
<iframe
|
||||
frameborder="0"
|
||||
style="height: {h}px; width: {w}px"
|
||||
class="p-0"
|
||||
title="sandbox"
|
||||
srcdoc={result
|
||||
? '<scr' + `ipt type="application/javascript" src="/tailwind.js"></script>` + result
|
||||
: 'No html'}
|
||||
srcdoc={result ? '<scr' + `ipt src="/tailwind.css"></script>` + result : 'No html'}
|
||||
/>
|
||||
{/key}
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Doughnut, Pie } from 'svelte-chartjs'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -18,7 +19,6 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
{#if doughnut}
|
||||
<Doughnut {data} {options} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -11,6 +12,7 @@
|
||||
CategoryScale,
|
||||
BarElement
|
||||
} from 'chart.js'
|
||||
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Scatter from 'svelte-chartjs/Scatter.svelte'
|
||||
@@ -19,7 +21,6 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
let zoomable = false
|
||||
let pannable = false
|
||||
@@ -69,7 +70,7 @@
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Clipboard } from 'lucide-svelte'
|
||||
import { copyToClipboard } from '../../../../utils'
|
||||
import { copyToClipboard } from '../../../../utils'
|
||||
import Button from '../../../common/button/Button.svelte'
|
||||
import Popover from '../../../Popover.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
@@ -13,7 +13,6 @@
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -60,23 +59,33 @@
|
||||
<InputValue {id} input={configuration.style} bind:value={style} />
|
||||
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if !result || result === ''}
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
No text
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
|
||||
<svelte:element this={component} class="whitespace-pre-wrap {classes}" style={extraStyle}>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<svelte:element
|
||||
this={component}
|
||||
class="whitespace-pre-wrap {classes}"
|
||||
style={extraStyle}
|
||||
>
|
||||
{String(result)}
|
||||
</svelte:element>
|
||||
{#if copyButton && result}
|
||||
<Popover notClickable>
|
||||
<Button size="xs" btnClasses="!px-2" on:click={() => copyToClipboard(result)}>
|
||||
<Button
|
||||
size="xs"
|
||||
btnClasses="!px-2"
|
||||
on:click={() => copyToClipboard(result)}
|
||||
>
|
||||
<Clipboard size={14} strokeWidth={2} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
|
||||
<svelte:fragment slot="text">
|
||||
Copy to clipboard
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||
import 'chartjs-adapter-date-fns'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -14,6 +15,7 @@
|
||||
TimeScale,
|
||||
LogarithmicScale
|
||||
} from 'chart.js'
|
||||
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Scatter from 'svelte-chartjs/Scatter.svelte'
|
||||
@@ -22,7 +24,6 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -84,7 +85,7 @@
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
{#if !Plotly}
|
||||
<div class="p-2">
|
||||
<Loader2 class="animate-spin" />
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -53,7 +52,7 @@
|
||||
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
{#if !vegaEmbed}
|
||||
<div class="p-2">
|
||||
<Loader2 class="animate-spin" />
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let runnableComponent: RunnableComponent
|
||||
let goto: string | undefined = undefined
|
||||
|
||||
let isLoading: boolean = false
|
||||
|
||||
@@ -49,7 +48,6 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.goto} bind:value={goto} />
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
@@ -59,7 +57,6 @@
|
||||
bind:runnableComponent
|
||||
bind:componentInput
|
||||
{id}
|
||||
{goto}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
forceSchemaDisplay={true}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import type { Schema } from '$lib/common'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
@@ -25,8 +24,6 @@
|
||||
export let defaultUserInput = false
|
||||
export let flexWrap = false
|
||||
export let wrapperClass = ''
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let gotoUrl: string | undefined = undefined
|
||||
|
||||
const {
|
||||
worldStore,
|
||||
@@ -299,7 +296,6 @@
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
}
|
||||
gotoUrl && gotoUrl != '' && result?.error == undefined && goto(gotoUrl)
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -309,7 +305,7 @@
|
||||
/>
|
||||
|
||||
<div class="h-full flex relative flex-row flex-wrap {wrapperClass}">
|
||||
{#if !initializing && autoRefresh === true}
|
||||
{#if autoRefresh === true}
|
||||
<div class="flex absolute top-1 right-1">
|
||||
<RefreshButton componentId={id} />
|
||||
</div>
|
||||
@@ -345,7 +341,7 @@
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="block grow w-full max-h-full">
|
||||
<div class="block grow max-h-full">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user