Compare commits

..

5 Commits

Author SHA1 Message Date
Ruben Fiszel
46ddbcc254 reformat package.json 2023-02-08 00:39:34 +01:00
Ruben Fiszel
fcab562bf2 update goto 2023-02-08 00:38:47 +01:00
Ruben Fiszel
56616d5c6d remove dist 2023-02-08 00:37:18 +01:00
Ruben Fiszel
471ec6b334 cleanup everything 2023-02-08 00:36:30 +01:00
Ruben Fiszel
56dd32b2f0 iife 2023-02-07 19:13:56 +01:00
44 changed files with 2085 additions and 304 deletions

View File

@@ -30,7 +30,7 @@ Open-source developer infrastructure for internal tools. Self-hostable alternati
**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>
@@ -175,7 +175,7 @@ back to the database is ~50ms. A typical lightweight deno job will take around
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
@@ -195,8 +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>
We publish helm charts at: <https://github.com/windmill-labs/windmill-helm-charts>
### Commercial license
@@ -232,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:
@@ -311,12 +309,14 @@ upcoming CLI tool.
| 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

View File

@@ -1638,21 +1638,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": [
@@ -3542,22 +3527,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": [
@@ -5157,6 +5126,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": [

View File

@@ -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},
@@ -96,7 +95,6 @@ pub struct OAuthConfig {
extra_params: Option<HashMap<String, String>>,
extra_params_callback: Option<HashMap<String, String>>,
req_body_auth: Option<bool>,
pkce: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -216,7 +214,6 @@ pub async fn build_oauth_clients(base_url: &str) -> anyhow::Result<AllClients> {
extra_params: None,
extra_params_callback: None,
req_body_auth: None,
pkce: None,
},
v.clone(),
false,
@@ -660,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?;
@@ -976,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)
@@ -998,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!(
@@ -1113,19 +1087,14 @@ fn oauth_redirect(
}
let mut auth_url = client.authorize_url(&state);
{
let mut query_string = auth_url.query_pairs_mut();
let pkce = PkceCodeVerifierS256::new_random();
for (key, value) in pkce.authorize_url_params() {
if let Some(extra_params) = extra_params {
let mut query_string = auth_url.query_pairs_mut();
for (key, value) in extra_params {
query_string.append_pair(&key, &value);
}
if let Some(extra_params) = extra_params {
for (key, value) in extra_params {
query_string.append_pair(&key, &value);
}
}
}
set_cookie(&state, cookies, is_secure);
Ok(Redirect::to(auth_url.as_str()))
}

View File

@@ -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)))

View File

@@ -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::{
@@ -926,7 +925,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 +946,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),

View File

@@ -1774,7 +1774,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")
};

View File

@@ -47,7 +47,7 @@ services:
environment:
- DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable
- BASE_URL=http://${WM_BASE_URL}
- BASE_INTERNAL_URL=http://windmill_server:8000
- BASE_INTERNAL_URL=http://localhost:8000
- RUST_LOG=info
- NUM_WORKERS=1
- DISABLE_SERVER=true

1
frontend/.gitignore vendored
View File

@@ -1,6 +1,7 @@
.DS_Store
node_modules
/build
/dist
/.svelte-kit
/package
/src/lib/gen

5
frontend/embed/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Build the app preview embed code
./embed/build.sh
serve dist

6
frontend/embed/build.sh Executable file
View File

@@ -0,0 +1,6 @@
mkdir -p dist
npm run build-embed
cp static/Inter-Variable.ttf dist
cp static/tailwind.js dist
npx tailwindcss -o src/lib/assets/app.css --minify -o dist/tailwind.css
cp embed/index.html dist

840
frontend/embed/index.html Normal file
View File

@@ -0,0 +1,840 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Embed demo</title>
<link href="/style.css" rel="stylesheet">
<link href="/tailwind.css" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="/appviewer.iife.js">
</script>
</script>
<script>
loadApp(document.getElementById("app"), {
"grid": [
{
"3": {
"h": 2,
"w": 1,
"x": 0,
"y": 0,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 5,
"w": 3,
"x": 0,
"y": 0,
"id": "a",
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "a",
"data": {
"id": "a",
"card": false,
"type": "htmlcomponent",
"softWrap": false,
"configuration": {},
"componentInput": {
"eval": "<img\n\tsrc=\"https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80\"\n>\n<div class=\"absolute top-4 left-2 text-white\">\n\tHello ${ctx.username}\n</div>\t\t\t\n",
"type": "template",
"value": "<img\n\tsrc=\"https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80\"\n>\n<div class=\"absolute top-4 left-2 text-white\">\n\tHello ${ctx.username}\n</div>\t\t\t\n",
"fieldType": "template"
}
}
},
{
"3": {
"h": 10,
"w": 3,
"x": 0,
"y": 2,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 10,
"w": 6,
"x": 3,
"y": 0,
"id": "b",
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "b",
"data": {
"id": "b",
"card": true,
"type": "tablecomponent",
"actionButtons": [],
"configuration": {
"search": {
"type": "static",
"value": "Disabled",
"fieldType": "select",
"onlyStatic": true,
"optionValuesKey": "tableSearchOptions"
}
},
"componentInput": {
"type": "static",
"value": [
{
"id": 1,
"age": 42,
"name": "A cell with a long name"
},
{
"id": 2,
"age": 84,
"name": "A briefer cell"
}
],
"fieldType": "array",
"subFieldType": "object"
}
}
},
{
"3": {
"h": 8,
"w": 2,
"x": 0,
"y": 12,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 8,
"w": 6,
"x": 0,
"y": 10,
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "c",
"data": {
"id": "c",
"card": true,
"type": "scatterchartcomponent",
"configuration": {
"pannable": {
"type": "static",
"value": false,
"fieldType": "boolean",
"onlyStatic": true
},
"zoomable": {
"type": "static",
"value": false,
"fieldType": "boolean",
"onlyStatic": true
}
},
"componentInput": {
"type": "static",
"value": [
{
"data": [
{
"x": 25,
"y": 50
},
{
"x": 23,
"y": 23
},
{
"x": 12,
"y": 37
}
],
"label": "foo",
"backgroundColor": "rgb(255, 12, 137)"
},
{
"data": [
{
"x": 32,
"y": 32
},
{
"x": 25,
"y": 42
},
{
"x": 3,
"y": 27
}
],
"label": "foobar",
"backgroundColor": "orange"
}
],
"fieldType": "array",
"subFieldType": "object"
}
}
},
{
"3": {
"h": 8,
"w": 2,
"x": 0,
"y": 20,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 8,
"w": 6,
"x": 6,
"y": 10,
"id": "d",
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "d",
"data": {
"id": "d",
"card": false,
"type": "displaycomponent",
"configuration": {},
"componentInput": {
"type": "connected",
"value": {
"foo": 42
},
"fields": {
"x": {
"type": "connected",
"value": "gooo",
"visible": true,
"fieldType": "string",
"connection": {
"path": "result",
"componentId": "e"
}
}
},
"runnable": {
"name": "Inline Script 1",
"type": "runnableByName",
"inlineScript": {
"path": "/inline-script/Inline Script 1",
"schema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"x"
],
"properties": {
"x": {
"type": "string",
"default": null,
"description": ""
}
}
},
"content": "// import * as wmill from \"https://deno.land/x/windmill@v1.57.1/mod.ts\"\n\nexport async function main(x: string) {\n return x\n}\n",
"language": "deno"
}
},
"fieldType": "object",
"connection": {
"path": "result",
"componentId": "e"
}
}
}
},
{
"3": {
"h": 1,
"w": 2,
"x": 1,
"y": 0,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 1,
"w": 3,
"x": 9,
"y": 0,
"id": "e",
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "e",
"data": {
"id": "e",
"card": false,
"type": "textinputcomponent",
"softWrap": true,
"configuration": {
"placeholder": {
"type": "static",
"value": "Type...",
"fieldType": "text",
"onlyStatic": true
}
},
"verticalAlignment": "center"
}
},
{
"3": {
"h": 1,
"w": 2,
"x": 1,
"y": 1,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 1,
"w": 3,
"x": 9,
"y": 1,
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "f",
"data": {
"id": "f",
"card": false,
"type": "passwordinputcomponent",
"softWrap": true,
"configuration": {
"placeholder": {
"type": "static",
"value": "Password",
"fieldType": "text",
"onlyStatic": true
}
},
"verticalAlignment": "center"
}
},
{
"3": {
"h": 1,
"w": 2,
"x": 0,
"y": 28,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 1,
"w": 3,
"x": 9,
"y": 2,
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "g",
"data": {
"id": "g",
"card": false,
"type": "dateinputcomponent",
"softWrap": true,
"configuration": {
"maxDate": {
"type": "static",
"value": "",
"fieldType": "date"
},
"minDate": {
"type": "static",
"value": "",
"fieldType": "date"
}
},
"verticalAlignment": "center"
}
},
{
"3": {
"h": 1,
"w": 1,
"x": 2,
"y": 12,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 1,
"w": 2,
"x": 9,
"y": 3,
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "h",
"data": {
"id": "h",
"card": false,
"type": "checkboxcomponent",
"softWrap": true,
"configuration": {
"label": {
"type": "static",
"value": "Label",
"fieldType": "text"
}
},
"verticalAlignment": "center",
"horizontalAlignment": "center"
}
},
{
"3": {
"h": 1,
"w": 2,
"x": 0,
"y": 29,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 1,
"w": 3,
"x": 9,
"y": 4,
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "i",
"data": {
"id": "i",
"card": false,
"type": "selectcomponent",
"softWrap": true,
"configuration": {
"items": {
"type": "static",
"value": [
{
"label": "Foo",
"value": "foo"
},
{
"label": "Bar",
"value": "bar"
}
],
"fieldType": "array",
"subFieldType": "object"
},
"itemKey": {
"type": "static",
"value": "value",
"fieldType": "text"
}
},
"verticalAlignment": "center"
}
},
{
"3": {
"h": 1,
"w": 1,
"x": 2,
"y": 13,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 1,
"w": 2,
"x": 0,
"y": 5,
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "j",
"data": {
"id": "j",
"card": false,
"type": "buttoncomponent",
"softWrap": true,
"configuration": {
"size": {
"type": "static",
"value": "xs",
"fieldType": "select",
"onlyStatic": true,
"optionValuesKey": "buttonSizeOptions"
},
"color": {
"type": "static",
"value": "blue",
"fieldType": "select",
"onlyStatic": true,
"optionValuesKey": "buttonColorOptions"
},
"label": {
"type": "static",
"value": "Press me",
"fieldType": "text"
}
},
"componentInput": {
"type": "runnable",
"fields": {},
"fieldType": "any"
},
"verticalAlignment": "center",
"horizontalAlignment": "center"
}
},
{
"3": {
"h": 5,
"w": 3,
"x": 0,
"y": 30,
"max": {
"h": 100,
"w": 3
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"12": {
"h": 5,
"w": 6,
"x": 0,
"y": 18,
"id": "k",
"max": {
"h": 100,
"w": 12
},
"min": {
"h": 1,
"w": 1
},
"fixed": false,
"draggable": true,
"resizable": true,
"customDragger": false,
"customResizer": false
},
"id": "k",
"data": {
"id": "k",
"card": true,
"type": "formcomponent",
"configuration": {
"size": {
"type": "static",
"value": "xs",
"fieldType": "select",
"onlyStatic": true,
"optionValuesKey": "buttonSizeOptions"
},
"color": {
"type": "static",
"value": "dark",
"fieldType": "select",
"onlyStatic": true,
"optionValuesKey": "buttonColorOptions"
},
"label": {
"type": "static",
"value": "Submit",
"fieldType": "text"
}
},
"componentInput": {
"type": "runnable",
"fields": {
"x": {
"type": "user",
"value": null,
"visible": true,
"fieldType": "string"
},
"y": {
"type": "user",
"value": null,
"visible": true,
"fieldType": "string"
}
},
"runnable": {
"name": "Inline Script 0",
"type": "runnableByName",
"inlineScript": {
"path": "/inline-script/Inline Script 0",
"schema": {
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "string",
"default": null,
"description": ""
},
"y": {
"type": "string",
"default": null,
"description": ""
}
}
},
"content": "// import * as wmill from \"https://deno.land/x/windmill@v1.57.1/mod.ts\"\n\nexport async function main(x: string, y: string) {\n return x\n}\n",
"language": "deno"
}
},
"fieldType": "any"
},
"horizontalAlignment": "center"
}
}
],
"fullscreen": false,
"unusedInlineScripts": []
})
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
"build-embed": "vite build -c vite.embed.config.js",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json --threshold warning",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
@@ -16,6 +17,11 @@
},
"devDependencies": {
"@playwright/test": "^1.29.2",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-html": "^1.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-typescript": "^11.0.0",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0-next.589",
"@sveltejs/package": "^1.0.2",
@@ -34,15 +40,17 @@
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-svelte3": "^4.0.0",
"lucide-svelte": "^0.104.0",
"monaco-editor-workers": "0.33.0",
"openapi-typescript-codegen": "^0.23.0",
"path-browserify": "^1.0.1",
"postcss": "^8.4.18",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"rollup-plugin-svelte": "^7.1.0",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
"svelte": "^3.53.1",
"svelte": "^3.55.1",
"svelte-awesome": "^3.0.0",
"svelte-check": "^3.0.2",
"svelte-dnd-action": "^0.9.22",
@@ -67,6 +75,7 @@
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chart.js": "3.8.2",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
@@ -83,5 +92,6 @@
},
"peerDependencies": {
"svelte": "^3.53.1"
}
},
"main": "index.js"
}

9
frontend/src/embed.js Normal file
View File

@@ -0,0 +1,9 @@
import AppPreview from './lib/components/apps/editor/AppPreview.svelte'
globalThis.process = { env: { NODE_ENV: 'production' } }
globalThis.loadApp = (target, value) => {
new AppPreview({
target: target,
props: { app: value }
})
}

View File

@@ -1,8 +1,6 @@
<script lang="ts">
import { truncate } from '$lib/utils'
import Tooltip from './Tooltip.svelte'
import json from 'svelte-highlight/languages/json'
import { Highlight } from 'svelte-highlight'
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import Drawer from './common/drawer/Drawer.svelte'

View File

@@ -21,7 +21,6 @@
| 'error'
| 'approval'
| 'svg'
| 'filename'
| undefined
$: resultKind = inferResultKind(result)
@@ -71,8 +70,6 @@
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') &&
@@ -186,12 +183,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

View File

@@ -19,7 +19,6 @@
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import { buildWorkerDefinition } from 'monaco-editor-workers'
import type {
Disposable,
DocumentUri,
@@ -90,13 +89,7 @@
const uri = `file:///tmp/monaco/${hash}.${langToExt(lang)}`
if (browser) {
if (dev) {
buildWorkerDefinition(
'../../../node_modules/monaco-editor-workers/dist/workers',
import.meta.url,
false
)
} else {
if (!dev) {
// @ts-ignore
self.MonacoEnvironment = {
getWorker: function (_moduleId: any, label: string) {
@@ -408,6 +401,14 @@
}
async function loadMonaco() {
if (dev) {
let { buildWorkerDefinition } = await import('monaco-editor-workers')
buildWorkerDefinition(
'../../../node_modules/monaco-editor-workers/dist/workers',
import.meta.url,
false
)
}
const model = meditor.createModel(code, lang, mUri.parse(uri))
model.updateOptions(updateOptions)

View File

@@ -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) {

View File

@@ -8,7 +8,6 @@
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
import { codeToStaticTemplate, getDefaultExpr, isCodeInjection } from './flows/utils'
import SimpleEditor from './SimpleEditor.svelte'
import { Button, ToggleButton, ToggleButtonGroup } from './common'
import { faCode } from '@fortawesome/free-solid-svg-icons'
import type VariableEditor from './VariableEditor.svelte'
@@ -17,6 +16,7 @@
import TemplateEditor from './TemplateEditor.svelte'
import Tooltip from './Tooltip.svelte'
import { setInputCat as computeInputCat } from '$lib/utils'
import JavascriptEditor from './JavascriptEditor.svelte'
export let schema: Schema
export let arg: InputTransform | any
@@ -29,7 +29,7 @@
export let itemPicker: ItemPicker | undefined = undefined
export let noDynamicToggle = false
let monaco: SimpleEditor | undefined = undefined
let monaco: JavascriptEditor | undefined = undefined
let monacoTemplate: TemplateEditor | undefined = undefined
let argInput: ArgInput | undefined = undefined
@@ -284,7 +284,7 @@
/>
{:else if arg.expr != undefined}
<div class="border rounded mt-2 border-gray-300">
<SimpleEditor
<JavascriptEditor
bind:this={monaco}
bind:code={arg.expr}
{extraLib}

View File

@@ -0,0 +1,212 @@
<script lang="ts">
import { browser, dev } from '$app/environment'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { createHash, editorConfig, langToExt, updateOptions } from '$lib/editorUtils'
import { languages, editor as meditor, KeyCode, KeyMod, Uri as mUri } from 'monaco-editor'
import libStdContent from '$lib/es5.d.ts.txt?raw'
meditor.defineTheme('myTheme', {
base: 'vs',
inherit: true,
rules: [],
colors: {
'editorLineNumber.foreground': '#999',
'editorGutter.background': '#F9FAFB'
}
})
meditor.setTheme('myTheme')
languages.typescript.javascriptDefaults.setCompilerOptions({
target: languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true,
noLib: true
})
let divEl: HTMLDivElement | null = null
let editor: meditor.IStandaloneCodeEditor
let model: meditor.ITextModel
export let lang: string
export let code: string = ''
export let hash: string = createHash()
export let cmdEnterAction: (() => void) | undefined = undefined
export let formatAction: (() => void) | undefined = undefined
export let automaticLayout = true
export let extraLib: string = ''
export let shouldBindKey: boolean = true
export let autoHeight = false
export let fixedOverflowWidgets = true
const dispatch = createEventDispatcher()
const uri = `file:///${hash}.${langToExt(lang)}`
if (browser) {
if (!dev) {
// @ts-ignore
self.MonacoEnvironment = {
getWorker: function (_moduleId: any, label: string) {
if (label === 'typescript' || label === 'javascript') {
return new tsWorker()
} else {
return new editorWorker()
}
}
}
}
}
export function getCode(): string {
return editor?.getValue() ?? ''
}
export function insertAtCursor(code: string): void {
if (editor) {
editor.trigger('keyboard', 'type', { text: code })
}
}
export function setCode(ncode: string): void {
code = ncode
if (editor) {
editor.setValue(ncode)
}
}
function format() {
if (editor) {
code = getCode()
editor.getAction('editor.action.formatDocument').run()
if (formatAction) {
formatAction()
code = getCode()
}
}
}
let width = 0
async function loadMonaco() {
if (dev) {
let { buildWorkerDefinition } = await import('monaco-editor-workers')
buildWorkerDefinition(
'../../../node_modules/monaco-editor-workers/dist/workers',
import.meta.url,
false
)
}
model = meditor.createModel(code, lang, mUri.parse(uri))
model.updateOptions(updateOptions)
editor = meditor.create(
divEl as HTMLDivElement,
editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets)
)
let timeoutModel: NodeJS.Timeout | undefined = undefined
editor.onDidChangeModelContent((event) => {
timeoutModel && clearTimeout(timeoutModel)
timeoutModel = setTimeout(() => {
code = getCode()
dispatch('change', { code })
}, 200)
})
editor.onDidFocusEditorText(() => {
dispatch('focus')
editor.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, function () {
code = getCode()
shouldBindKey && format && format()
})
})
if (autoHeight) {
let ignoreEvent = false
const updateHeight = () => {
const contentHeight = Math.min(1000, editor.getContentHeight())
if (divEl) {
divEl.style.height = `${contentHeight}px`
}
try {
ignoreEvent = true
editor.layout({ width, height: contentHeight })
} finally {
ignoreEvent = false
}
}
editor.onDidContentSizeChange(updateHeight)
updateHeight()
}
editor.onDidFocusEditorText(() => {
editor.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, function () {
code = getCode()
shouldBindKey && format && format()
})
editor.addCommand(KeyMod.CtrlCmd | KeyCode.Enter, function () {
code = getCode()
shouldBindKey && cmdEnterAction && cmdEnterAction()
})
dispatch('focus')
})
editor.onDidBlurEditorText(() => {
code = getCode()
dispatch('blur')
})
if (lang == 'javascript') {
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
if (extraLib != '') {
languages.typescript.javascriptDefaults.setExtraLibs([
{
content: extraLib,
filePath: 'windmill.d.ts'
},
stdLib
])
} else {
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
}
}
}
onMount(() => {
if (browser) {
loadMonaco()
}
})
onDestroy(() => {
try {
model && model.dispose()
editor && editor.dispose()
} catch (err) {}
})
</script>
<div bind:this={divEl} class="{$$props.class} editor" bind:clientWidth={width} />
<style>
.editor {
@apply rounded-lg p-0;
}
.small-editor {
/* stylelint-disable-next-line unit-allowed-list */
height: 26vh;
}
.few-lines-editor {
/* stylelint-disable-next-line unit-allowed-list */
height: 100px;
}
</style>

View File

@@ -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}
/>

View File

@@ -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>

View File

@@ -7,11 +7,9 @@
import { slide } from 'svelte/transition'
import ArgInput from './ArgInput.svelte'
import { Button } from './common'
import InputTransformForm from './InputTransformForm.svelte'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
export let inputTransform = false
export let schema: Schema
export let args: Record<string, InputTransform | any> = {}
export let disabledArgs: string[] = []
@@ -19,16 +17,13 @@
export let editableSchema = false
export let isValid: boolean = true
export let extraLib: string = 'missing extraLib'
export let autofocus = false
export let previousModuleId: string | undefined = undefined
export let shouldHideNoInputs: boolean = false
export let compact = false
export let password: string | undefined = undefined
export let noVariablePicker = false
export let filter: string[] | undefined = undefined
export let noDynamicToggle = false
export let flexWrap = false
export let noDelete = false
@@ -71,20 +66,7 @@
{#each keys as argName, i (argName)}
{#if !filter || filter.includes(argName)}
<div transition:slide|local>
{#if inputTransform}
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
bind:schema
bind:argName
bind:inputCheck={inputCheck[argName]}
bind:extraLib
{variableEditor}
{itemPicker}
bind:pickForField
{noDynamicToggle}
/>
{:else if typeof args == 'object'}
{#if typeof args == 'object'}
<ArgInput
autofocus={i == 0 && autofocus}
label={argName}
@@ -125,11 +107,7 @@
bind:this={itemPicker}
pickCallback={(path, _) => {
if (pickForField) {
if (inputTransform) {
args[pickForField].value = '$var:' + path
} else {
args[pickForField] = '$var:' + path
}
args[pickForField] = '$var:' + path
}
}}
itemName="Variable"

View File

@@ -0,0 +1,120 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { VariableService, type InputTransform } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue } from '$lib/utils'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { slide } from 'svelte/transition'
import { Button } from './common'
import InputTransformForm from './InputTransformForm.svelte'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
export let schema: Schema
export let args: Record<string, InputTransform | any> = {}
export let isValid: boolean = true
export let extraLib: string = 'missing extraLib'
export let previousModuleId: string | undefined = undefined
export let shouldHideNoInputs: boolean = false
export let noVariablePicker = false
export let filter: string[] | undefined = undefined
export let noDynamicToggle = false
export let flexWrap = false
export let noDelete = false
let clazz: string = ''
export { clazz as class }
let inputCheck: { [id: string]: boolean } = {}
$: isValid = allTrue(inputCheck) ?? false
$: if (args == undefined || typeof args !== 'object') {
args = {}
}
function removeExtraKey() {
Object.keys(args ?? {}).forEach((key) => {
if (!keys.includes(key)) {
delete args[key]
delete inputCheck[key]
}
})
}
let pickForField: string | undefined
let itemPicker: ItemPicker | undefined = undefined
let variableEditor: VariableEditor | undefined = undefined
let keys: string[] = []
$: {
let lkeys = Object.keys(schema?.properties ?? {})
if (schema?.properties && JSON.stringify(lkeys) != JSON.stringify(keys)) {
keys = lkeys
if (!noDelete) {
removeExtraKey()
}
}
}
</script>
<div class="w-full {clazz} {flexWrap ? 'flex flex-row flex-wrap gap-x-6 gap-y-2' : ''}">
{#if keys.length > 0}
{#each keys as argName, i (argName)}
{#if !filter || filter.includes(argName)}
<div transition:slide|local>
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
bind:schema
bind:argName
bind:inputCheck={inputCheck[argName]}
bind:extraLib
{variableEditor}
{itemPicker}
bind:pickForField
{noDynamicToggle}
/>
</div>
{/if}
{/each}
{:else if !shouldHideNoInputs}
<div class="text-gray-500 text-sm">No inputs</div>
{/if}
</div>
{#if !noVariablePicker}
<ItemPicker
bind:this={itemPicker}
pickCallback={(path, _) => {
if (pickForField) {
args[pickForField].value = '$var:' + path
}
}}
itemName="Variable"
extraField="path"
loadItems={async () =>
(await VariableService.listVariable({ workspace: $workspaceStore ?? '' })).map((x) => ({
name: x.path,
...x
}))}
>
<div
slot="submission"
class="flex flex-row-reverse w-full bg-white border-t border-gray-200 rounded-bl-lg rounded-br-lg"
>
<Button
variant="border"
color="blue"
size="sm"
startIcon={{ icon: faPlus }}
on:click={() => {
variableEditor?.initNew?.()
}}
>
New variable
</Button>
</div>
</ItemPicker>
<VariableEditor bind:this={variableEditor} />
{/if}

View File

@@ -213,7 +213,7 @@
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>
<div class="flex flex-row gap-2 flex-wrap">

View File

@@ -4,15 +4,16 @@
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import yamlWorker from 'monaco-yaml/yaml.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import { buildWorkerDefinition } from 'monaco-editor-workers'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { createHash, editorConfig, langToExt, updateOptions } from '$lib/editorUtils'
import { languages, editor as meditor, KeyCode, KeyMod, Uri as mUri } from 'monaco-editor'
import libStdContent from '$lib/es5.d.ts.txt?raw'
import {
languages,
editor as meditor,
KeyCode,
KeyMod,
Uri as mUri
} from 'monaco-editor/esm/vs/editor/editor.main'
meditor.defineTheme('myTheme', {
base: 'vs',
@@ -25,12 +26,6 @@
})
meditor.setTheme('myTheme')
languages.typescript.javascriptDefaults.setCompilerOptions({
target: languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true,
noLib: true
})
languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
allowComments: false,
@@ -48,7 +43,6 @@
export let cmdEnterAction: (() => void) | undefined = undefined
export let formatAction: (() => void) | undefined = undefined
export let automaticLayout = true
export let extraLib: string = ''
export let shouldBindKey: boolean = true
export let autoHeight = false
export let fixedOverflowWidgets = true
@@ -58,13 +52,7 @@
const uri = `file:///${hash}.${langToExt(lang)}`
if (browser) {
if (dev) {
buildWorkerDefinition(
'../../../node_modules/monaco-editor-workers/dist/workers',
import.meta.url,
false
)
} else {
if (!dev) {
// @ts-ignore
self.MonacoEnvironment = {
getWorker: function (_moduleId: any, label: string) {
@@ -72,8 +60,6 @@
return new jsonWorker()
} else if (label === 'yaml') {
return new yamlWorker()
} else if (label === 'typescript' || label === 'javascript') {
return new tsWorker()
} else {
return new editorWorker()
}
@@ -174,21 +160,6 @@
code = getCode()
dispatch('blur')
})
if (lang == 'javascript') {
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
if (extraLib != '') {
languages.typescript.javascriptDefaults.setExtraLibs([
{
content: extraLib,
filePath: 'windmill.d.ts'
},
stdLib
])
} else {
languages.typescript.javascriptDefaults.setExtraLibs([stdLib])
}
}
}
onMount(() => {

View File

@@ -12,19 +12,24 @@
EditorMode
} from '../types'
import GridEditor from './GridEditor.svelte'
import { classNames } from '$lib/utils'
import { classNames } from '../../../utils'
import type { Policy } from '$lib/gen'
import Button from '../../common/button/Button.svelte'
import { Unlock } from 'lucide-svelte'
export let app: App
export let appPath: string
export let breakpoint: Writable<EditorBreakpoint>
export let policy: Policy
export let summary: string
export let workspace: string
export let isEditor: boolean
export let context: Record<string, any>
export let app: App = {
grid: [],
fullscreen: false,
unusedInlineScripts: [],
hiddenInlineScripts: []
}
export let appPath: string = ''
export let breakpoint: Writable<EditorBreakpoint> = writable('lg')
export let policy: Policy = {}
export let summary: string = ''
export let workspace: string = 'workspace'
export let isEditor: boolean = false
export let context: Record<string, any> = {}
export let noBackend: boolean = false
export let isLocked = false

View File

@@ -3,8 +3,8 @@
import { getContext } from 'svelte'
import type { AppEditorContext } from '$lib/components/apps/types'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { buildExtraLib } from '$lib/components/apps/utils'
import JavascriptEditor from '$lib/components/JavascriptEditor.svelte'
export let componentInput: EvalAppInput | undefined
export let id: string
@@ -22,7 +22,7 @@
{#if componentInput?.type === 'eval'}
<div class="border border-gray-300 ">
<SimpleEditor
<JavascriptEditor
lang="javascript"
bind:code={componentInput.expr}
shouldBindKey={false}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { classNames } from '$lib/utils'
import { classNames } from '../../../utils'
import Icon from 'svelte-awesome'
import { ButtonType } from './model'
import { goto } from '$app/navigation'

View File

@@ -13,8 +13,8 @@
let pendingJson: string
async function importJson() {
$importFlowStore = JSON.parse(pendingJson)
await goto('/flows/add')
$importFlowStore = JSON.parse(pendingJson)
drawer?.closeDrawer?.()
}
</script>

View File

@@ -3,11 +3,11 @@
import { getContext } from 'svelte'
import { flowStore } from '../flowStore'
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { flowStateStore } from '../flowState'
import { getStepPropPicker } from '../previousResults'
import type { FlowEditorContext } from '../types'
import JavascriptEditor from '$lib/components/JavascriptEditor.svelte'
export let branch: {
summary?: string
@@ -18,7 +18,7 @@
export let previousModule: FlowModule | undefined
const { previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
let editor: SimpleEditor | undefined = undefined
let editor: JavascriptEditor | undefined = undefined
$: stepPropPicker = getStepPropPicker(
$flowStateStore,
@@ -40,7 +40,7 @@
}}
>
<div class="border border-gray-400">
<SimpleEditor
<JavascriptEditor
bind:this={editor}
lang="javascript"
bind:code={branch.expr}

View File

@@ -3,13 +3,13 @@
import FlowCard from '../common/FlowCard.svelte'
import { Alert, Badge } from '$lib/components/common'
import type { FlowModule, FlowModuleValue, InputTransform, PathScript, RawScript } from '$lib/gen'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import { flowStateStore } from '../flowState'
import { setContext } from 'svelte'
import type { PropPickerWrapperContext } from '../propPicker/PropPickerWrapper.svelte'
import { writable } from 'svelte/store'
import { slide } from 'svelte/transition'
import Toggle from '../../Toggle.svelte'
import SchemaFormTransform from '$lib/components/SchemaFormTransform.svelte'
let hideOptional = false
@@ -37,7 +37,7 @@
] as [Record<string, InputTransform>, string[], FlowModule]
)
.filter(([i, f, m]) => f.length > 0)
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
focusProp: () => {},
propPickerConfig: writable(undefined),
@@ -47,11 +47,7 @@
<div class="min-h-full">
<FlowCard title="All Static Inputs">
<Toggle
slot="header"
bind:checked={hideOptional}
options={{left: 'Hide optional inputs'}}
/>
<Toggle slot="header" bind:checked={hideOptional} options={{ left: 'Hide optional inputs' }} />
<div class="min-h-full flex-1">
<Alert type="info" title="Static Inputs" class="m-4"
>This page centralizes the static inputs of every steps. It is akin to a file containing all
@@ -80,9 +76,8 @@
<Badge large color="indigo">{m.id}</Badge>
</h2>
<SchemaForm
<SchemaFormTransform
noDynamicToggle
inputTransform
{filter}
class="mt-2"
schema={$flowStateStore[m.id]?.schema ?? {}}

View File

@@ -3,7 +3,6 @@
import FlowCard from '../common/FlowCard.svelte'
import type { FlowEditorContext } from '../types'
import Toggle from '$lib/components/Toggle.svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
import FlowModuleEarlyStop from './FlowModuleEarlyStop.svelte'
@@ -16,6 +15,7 @@
import { flowStateStore } from '../flowState'
import { flowStore } from '../flowStore'
import FlowModuleSleep from './FlowModuleSleep.svelte'
import JavascriptEditor from '$lib/components/JavascriptEditor.svelte'
const { previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
@@ -23,7 +23,7 @@
export let parentModule: FlowModule | undefined
export let previousModule: FlowModule | undefined
let editor: SimpleEditor | undefined = undefined
let editor: JavascriptEditor | undefined = undefined
let selected: string = 'early-stop'
$: stepPropPicker = getStepPropPicker(
@@ -50,7 +50,7 @@
Iterator expression
<Tooltip>
List to iterate over. For more information see the
<a href="https://docs.windmill.dev/docs/getting_started/flows_quickstart">docs.</a>
<a href="https://docs.windmill.dev/docs/getting_started/flows#for-loops">docs.</a>
</Tooltip>
</div>
{#if mod.value.iterator.type == 'javascript'}
@@ -62,7 +62,7 @@
editor?.insertAtCursor(detail)
}}
>
<SimpleEditor
<JavascriptEditor
bind:this={editor}
lang="javascript"
bind:code={mod.value.iterator.expr}

View File

@@ -7,7 +7,6 @@
import ModulePreview from '$lib/components/ModulePreview.svelte'
import { createScriptFromInlineScript, fork } from '$lib/components/flows/flowStateUtils'
import { flowStore } from '$lib/components/flows/flowStore'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import { RawScript, type FlowModule, type PathFlow, type PathScript } from '$lib/gen'
import FlowCard from '../common/FlowCard.svelte'
import FlowModuleHeader from './FlowModuleHeader.svelte'
@@ -27,6 +26,7 @@
import Alert from '$lib/components/common/alert/Alert.svelte'
import FlowModuleSleep from './FlowModuleSleep.svelte'
import FlowPathViewer from './FlowPathViewer.svelte'
import SchemaFormTransform from '$lib/components/SchemaFormTransform.svelte'
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
@@ -211,9 +211,8 @@
pickableProperties={stepPropPicker.pickableProperties}
error={failureModule}
>
<SchemaForm
<SchemaFormTransform
schema={$flowStateStore[$selectedId]?.schema ?? {}}
inputTransform={true}
previousModuleId={previousModule?.id}
bind:args={value.input_transforms}
bind:extraLib={stepPropPicker.extraLib}

View File

@@ -1,15 +1,15 @@
<script lang="ts">
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import PropPickerWrapper from '$lib/components/flows/propPicker/PropPickerWrapper.svelte'
import type { FlowModule } from '$lib/gen'
import { flowStateStore } from '../flowState'
import Tooltip from '$lib/components/Tooltip.svelte'
import { NEVER_TESTED_THIS_FAR } from '../utils'
import JavascriptEditor from '$lib/components/JavascriptEditor.svelte'
export let flowModule: FlowModule
let editor: SimpleEditor | undefined = undefined
let editor: JavascriptEditor | undefined = undefined
$: isStopAfterIfEnabled = Boolean(flowModule.stop_after_if)
@@ -59,7 +59,7 @@
editor?.insertAtCursor(detail)
}}
>
<SimpleEditor
<JavascriptEditor
bind:this={editor}
lang="javascript"
bind:code={flowModule.stop_after_if.expr}

View File

@@ -54,7 +54,7 @@
<input
type="text"
bind:value={$flowStore.summary}
placeholder="Short summary to be displayed when listed"
placeholder="A short summary of the flow displayed when the flow is listed"
id="flow-summary"
/>
</label>
@@ -69,7 +69,7 @@
class="text-sm"
id="inp"
bind:value={$flowStore.description}
placeholder="What this flow does and how to use it."
placeholder="A description to help users understand what this flow does and how to use it."
rows="3"
/>
</span>

View File

@@ -22,7 +22,6 @@
import { createEventDispatcher } from 'svelte'
import { charsToNumber, numberToChars } from '../flows/utils'
export let success: boolean | undefined = undefined
export let modules: FlowModule[] | undefined = []
export let failureModule: FlowModule | undefined = undefined
export let minHeight: number = 0
@@ -120,13 +119,7 @@
} else if (type === 'forloopflow') {
return flowModuleToLoop(module.value.modules, module, parent)
} else if (type === 'branchone') {
const branches = [
{ summary: 'Default branch', modules: module.value.default },
...module.value.branches.map((b, i) => ({
summary: b.summary ?? 'Branch ' + (i + 1),
modules: b.modules
}))
]
const branches = [module.value.default, ...module.value.branches.map((b) => b.modules)]
return flowModuleToBranch(
module,
branches,
@@ -135,10 +128,7 @@
insideLoop
)
} else if (type === 'branchall') {
const branches = module.value.branches.map((b, i) => ({
summary: b.summary ?? `Branch ${i + 1}`,
modules: b.modules
}))
const branches = module.value.branches.map((b) => b.modules)
return flowModuleToBranch(module, branches, [], parent, insideLoop)
} else if (type === 'flow') {
return flowModuleToNode(
@@ -200,17 +190,6 @@
return '#fff'
}
}
function getResultColor(): string | undefined {
switch (success) {
case true:
return getStateColor(FlowStatusModule.type.SUCCESS)
case false:
return getStateColor(FlowStatusModule.type.FAILURE)
default:
return undefined
}
}
function flowModuleToNode(
parentIds: string[],
id: string,
@@ -318,7 +297,7 @@
function flowModuleToBranch(
module: FlowModule,
branches: { summary: string; modules: FlowModule[] }[],
branches: FlowModule[][],
edgesLabel: string[],
parent: string | NestedNodes | undefined = undefined,
insideLoop: boolean = false
@@ -344,12 +323,18 @@
if (branches.length == 0) {
branch.items.push([createVirtualNode(branchParent, 'No branches')])
}
branches.forEach(({ summary, modules }, i) => {
branches.forEach((modules, i) => {
const items: NestedNodes = []
items.push(createVirtualNode(branchParent, summary, edgesLabel[i]))
if (modules.length) {
if (!modules.length) {
items.push(createVirtualNode(branchParent, 'Empty branch', edgesLabel[i]))
} else {
modules.forEach((module, j) => {
const item = getConvertedFlowModule(module, items, undefined, insideLoop)
const item = getConvertedFlowModule(
module,
items.length ? items : numberToChars(branch.node.id),
j == 0 ? edgesLabel[i] : undefined,
insideLoop
)
item && items.push(item)
})
}
@@ -446,7 +431,7 @@
function createVirtualNode(
parentIds: string[],
label: string,
edgeLabel?: string,
edgesLabel?: string,
offset?: number
): Node {
const id = -idGenerator.next().value - 1 + (offset ?? 0)
@@ -463,15 +448,14 @@
width: NODE.width,
height: NODE.height,
borderColor: selectedNode == label ? 'black' : '#999',
bgColor: label == 'Result' ? getResultColor() : selectedNode == label ? '#f5f5f5' : '#d4e4ff',
bgColor: selectedNode == label ? '#f5f5f5' : '#d4e4ff',
parentIds,
clickCallback: (node) => {
if (!notSelectable) {
selectedNode = label
}
dispatch('click', label)
},
edgeLabel
}
}
}

View File

@@ -0,0 +1,12 @@
import { readable } from 'svelte/store'
export async function goto(x) {
console.error('Goto not implemented')
}
export const browser = true
export const dev = true
export const page = readable({ url: document.URL }, function start(set) {
return function stop() {}
})

View File

@@ -9,7 +9,7 @@ import {
UserService,
type Flow,
type User
} from '$lib/gen'
} from '$lib/gen/index'
import { toast } from '@zerodevx/svelte-toast'
import type { Schema, SupportedLanguage } from './common'
import { hubScripts, workspaceStore, type UserExt } from './stores'

View File

@@ -307,7 +307,7 @@
>Webhook<Tooltip>
Pass the input as a json payload, the token as a Bearer token or as query arg
`?token=XXX` and pass as header: 'Content-Type: application/json <a
href="https://docs.windmill.dev/docs/core_concepts/webhooks">See docs</a
href="https://docs.windmill.dev/docs/getting_started/webhooks">See docs</a
></Tooltip
></h2
>

View File

@@ -28,7 +28,7 @@
} else {
sendUserToast('Missing code or state as query params', true)
}
goto('/workspace_settings?tab=slack')
goto('/workspace_settings')
})
</script>

View File

@@ -448,7 +448,7 @@
<Tooltip>
Pass the input as a json payload, the token as a Bearer token or as query arg
`?token=XXX` and pass as header: 'Content-Type: application/json'
<a href="https://docs.windmill.dev/docs/core_concepts/webhooks" class="text-blue-500">
<a href="https://docs.windmill.dev/docs/getting_started/webhooks" class="text-blue-500">
See docs
</a>
</Tooltip>

View File

@@ -0,0 +1,59 @@
// import { sveltekit } from '@sveltejs/kit/vite'
import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import { svelte } from '@sveltejs/vite-plugin-svelte'
const file = fileURLToPath(new URL('package.json', import.meta.url))
const json = readFileSync(file, 'utf8')
const version = JSON.parse(json)
/** @type {import('vite').UserConfig} */
const config = {
server: {
port: 3000
},
preview: {
port: 3000
},
plugins: [svelte()],
define: {
__pkg__: version
},
optimizeDeps: {
include: ['highlight.js', 'highlight.js/lib/core']
},
resolve: {
alias: {
path: 'path-browserify',
$lib: ['/src/lib'],
'$lib/*': ['/src/lib/*'],
'$app/navigation': ['/src/lib/fakesvelte.js'],
'$app/stores': ['/src/lib/fakesvelte.js'],
'$app/environment': ['/src/lib/fakesvelte.js']
}
},
build: {
lib: {
// Could also be a dictionary or array of multiple entry points
entry: ['src/embed.js'],
name: 'AppViewer',
// the proper extensions will be added
fileName: 'appviewer',
formats: ['iife']
}
// rollupOptions: {
// external: ['highlight.js'],
// input: 'src/embed.js',
// output: {
// format: 'esm',
// name: 'app',
// // We output it to public. This way, our svelte kit
// // app will also host the web components.
// dir: 'build'
// // file: 'build/app.js'
// }
// }
}
}
export default config

View File

@@ -2,7 +2,7 @@ DO
$do$
BEGIN
IF EXISTS (
select usesuper from pg_user where (usename = CURRENT_USER AND usesuper = 't')) OR (CURRENT_USER = 'cloudsqlsuperuser')
select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't')
AND NOT EXISTS (
SELECT
FROM pg_catalog.pg_roles
@@ -35,7 +35,7 @@ $do$;
DO
$do$
BEGIN
IF EXISTS (select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't') OR (CURRENT_USER = 'cloudsqlsuperuser')
IF EXISTS (select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't')
AND NOT EXISTS (
SELECT
FROM pg_catalog.pg_roles