Merge branch 'main' into alp/teams_scripts

This commit is contained in:
Alex Petric
2025-02-03 15:18:24 -05:00
59 changed files with 747 additions and 501 deletions
+1 -1
View File
@@ -7,4 +7,4 @@ CaddyfileRemoteMalo
*.swp
**/.idea/
.direnv
.vscode
.vscode
+21
View File
@@ -1,5 +1,26 @@
# Changelog
## [1.456.0](https://github.com/windmill-labs/windmill/compare/v1.455.2...v1.456.0) (2025-02-01)
### Features
* flow history picker for flow status + load last flow state ([611d5e8](https://github.com/windmill-labs/windmill/commit/611d5e8bf3d64106c3e9698687d2ec3710988517))
### Bug Fixes
* only start smtp servers if email domain is set ([6d9edc8](https://github.com/windmill-labs/windmill/commit/6d9edc8c2b01c99339c156dc84def13b0a2205a6))
## [1.455.2](https://github.com/windmill-labs/windmill/compare/v1.455.1...v1.455.2) (2025-01-31)
### Bug Fixes
* fix worker restart on otel setting set from undefined to null ([#5183](https://github.com/windmill-labs/windmill/issues/5183)) ([940fb03](https://github.com/windmill-labs/windmill/commit/940fb030232ea7944db94bfdf8b30a96f30baa21))
* improve autoscaling num workers inc ([2638dfc](https://github.com/windmill-labs/windmill/commit/2638dfcf00522af733c49b2c02a78ec4028623e0))
* improve history and saved inputs rever mechanism ([0b0e564](https://github.com/windmill-labs/windmill/commit/0b0e5640d8fa9682ad0a4349c863b743321be1f4))
## [1.455.1](https://github.com/windmill-labs/windmill/compare/v1.455.0...v1.455.1) (2025-01-31)
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM _sqlx_migrations WHERE version=20250131115248",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "fe58ca3cef78c73867d309156df2b74f7127bf6cba25c9a0c5ca5eedd6a19aff"
}
+33 -33
View File
@@ -590,9 +590,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.85"
version = "0.1.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
dependencies = [
"proc-macro2",
"quote",
@@ -1552,9 +1552,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.10"
version = "1.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
dependencies = [
"jobserver",
"libc",
@@ -3072,9 +3072,9 @@ dependencies = [
[[package]]
name = "dyn-clone"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35"
[[package]]
name = "dyn-iter"
@@ -10123,9 +10123,9 @@ dependencies = [
[[package]]
name = "tree-sitter-language"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c199356c799a8945965bb5f2c55b2ad9d9aa7c4b4f6e587fe9dea0bc715e5f9c"
checksum = "38eee4db33814de3d004de9d8d825627ed3320d0989cce0dea30efaf5be4736c"
[[package]]
name = "triomphe"
@@ -10860,7 +10860,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"axum",
@@ -10903,7 +10903,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"argon2",
@@ -10996,7 +10996,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"base64 0.22.1",
"chrono",
@@ -11014,7 +11014,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"chrono",
"serde",
@@ -11027,7 +11027,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"serde",
@@ -11041,7 +11041,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"async-stream",
@@ -11100,7 +11100,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"regex",
"serde",
@@ -11114,7 +11114,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"bytes",
@@ -11137,7 +11137,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -11149,7 +11149,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -11158,7 +11158,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11170,7 +11170,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"serde_json",
@@ -11182,7 +11182,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"gosyn",
@@ -11194,7 +11194,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11206,7 +11206,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -11217,7 +11217,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -11228,7 +11228,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11248,7 +11248,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -11265,7 +11265,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11277,7 +11277,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -11295,7 +11295,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -11317,7 +11317,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"serde_json",
@@ -11327,7 +11327,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -11360,7 +11360,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -11370,7 +11370,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.455.1"
version = "1.456.0"
dependencies = [
"anyhow",
"async-recursion",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.455.1"
version = "1.456.0"
authors.workspace = true
edition.workspace = true
@@ -30,7 +30,7 @@ members = [
]
[workspace.package]
version = "1.455.1"
version = "1.456.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
+1 -1
View File
@@ -1 +1 @@
7cc103e5827b1ec91180a5939a9c022a2a1856c6
15ddca63acd769e4e7355269a239639c50989def
@@ -1 +0,0 @@
INSERT INTO global_settings (name, value) VALUES ('otel', '{}');
@@ -0,0 +1,3 @@
INSERT INTO global_settings (name, value)
VALUES ('otel', '{}')
ON CONFLICT (name) DO NOTHING;
+41 -41
View File
@@ -17,13 +17,11 @@ use rand::Rng;
use sqlx::{postgres::PgListener, Pool, Postgres};
use std::{
collections::HashMap,
fs::{create_dir_all, DirBuilder},
net::{IpAddr, Ipv4Addr, SocketAddr},
time::Duration,
};
use tokio::{
fs::{create_dir_all, DirBuilder, File},
io::AsyncReadExt,
};
use tokio::{fs::File, io::AsyncReadExt};
use uuid::Uuid;
use windmill_api::HTTP_CLIENT;
@@ -34,20 +32,20 @@ use windmill_common::{
global_settings::{
BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING,
CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING,
EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING,
INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING,
NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING,
TIMEOUT_WAIT_RESULT_SETTING,
DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING,
HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING,
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING,
LICENSE_KEY_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPM_CONFIG_REGISTRY_SETTING,
NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, PIP_INDEX_URL_SETTING,
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING,
TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
},
scripts::ScriptLang,
stats_ee::schedule_stats,
utils::{hostname, rd_string, Mode, GIT_VERSION},
worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, WORKER_GROUP},
worker::{reload_custom_tags_setting, HUB_CACHE_DIR, TMP_DIR, TMP_LOGS_DIR, WORKER_GROUP},
DB, METRICS_ENABLED,
};
@@ -73,7 +71,7 @@ use windmill_worker::{
DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR,
POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR,
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TAR_PY310_CACHE_DIR, TAR_PY311_CACHE_DIR,
TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, TMP_LOGS_DIR, UV_CACHE_DIR,
TAR_PY312_CACHE_DIR, TAR_PY313_CACHE_DIR, UV_CACHE_DIR,
};
use crate::monitor::{
@@ -140,8 +138,8 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
)
})?;
create_dir_all(HUB_CACHE_DIR).await?;
create_dir_all(BUN_BUNDLE_CACHE_DIR).await?;
create_dir_all(HUB_CACHE_DIR)?;
create_dir_all(BUN_BUNDLE_CACHE_DIR)?;
for path in paths.values() {
tracing::info!("Caching hub script at {path}");
@@ -152,7 +150,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
.is_some_and(|x| x == &ScriptLang::Deno)
{
let job_dir = format!("{}/cache_init/{}", TMP_DIR, Uuid::new_v4());
create_dir_all(&job_dir).await?;
create_dir_all(&job_dir)?;
let _ = windmill_worker::generate_deno_lock(
&Uuid::nil(),
&res.content,
@@ -170,7 +168,7 @@ async fn cache_hub_scripts(file_path: Option<String>) -> anyhow::Result<()> {
} else if res.language.as_ref().is_some_and(|x| x == &ScriptLang::Bun) {
let job_id = Uuid::new_v4();
let job_dir = format!("{}/cache_init/{}", TMP_DIR, job_id);
create_dir_all(&job_dir).await?;
create_dir_all(&job_dir)?;
if let Some(lockfile) = res.lockfile {
let _ = windmill_worker::prepare_job_dir(&lockfile, &job_dir).await?;
let envs = windmill_worker::get_common_bun_proc_envs(None).await;
@@ -505,7 +503,6 @@ Windmill Community Edition {GIT_VERSION}
DirBuilder::new()
.recursive(true)
.create("/tmp/windmill")
.await
.expect("could not create initial server dir");
#[cfg(feature = "tantivy")]
@@ -788,11 +785,12 @@ Windmill Community Edition {GIT_VERSION}
},
EXPOSE_METRICS_SETTING => {
tracing::info!("Metrics setting changed, restarting");
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
let rd_delay = rand::rng().random_range(0..40);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
send_delayed_killpill(&tx, 40, "metrics setting change").await;
},
EMAIL_DOMAIN_SETTING => {
tracing::info!("Email domain setting changed");
if server_mode {
send_delayed_killpill(&tx, 4, "email domain setting change").await;
}
},
EXPOSE_DEBUG_METRICS_SETTING => {
@@ -802,29 +800,17 @@ Windmill Community Edition {GIT_VERSION}
},
OTEL_SETTING => {
tracing::info!("OTEL setting changed, restarting");
// we wait a bit randomly to avoid having all servers and workers shutdown at same time
let rd_delay = rand::rng().random_range(0..4);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill");
}
send_delayed_killpill(&tx, 4, "OTEL setting change").await;
},
REQUEST_SIZE_LIMIT_SETTING => {
if server_mode {
tracing::info!("Request limit size change detected, killing server expecting to be restarted");
// we wait a bit randomly to avoid having all servers shutdown at same time
let rd_delay = rand::rng().random_range(0..4);
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
}
send_delayed_killpill(&tx, 4, "request size limit change").await;
}
},
SAML_METADATA_SETTING => {
tracing::info!("SAML metadata change detected, killing server expecting to be restarted");
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill to server");
}
send_delayed_killpill(&tx, 0, "SAML metadata change").await;
},
HUB_BASE_URL_SETTING => {
if let Err(e) = reload_hub_base_url_setting(&db, server_mode).await {
@@ -1047,7 +1033,6 @@ pub async fn run_workers(
DirBuilder::new()
.recursive(true)
.create(x)
.await
.expect("could not create initial worker dir");
}
@@ -1098,3 +1083,18 @@ pub async fn run_workers(
futures::future::try_join_all(handles).await?;
Ok(())
}
async fn send_delayed_killpill(
tx: &tokio::sync::broadcast::Sender<()>,
max_delay_secs: u64,
context: &str,
) {
// Random delay to avoid all servers/workers shutting down simultaneously
let rd_delay = rand::rng().random_range(0..max_delay_secs);
tracing::info!("Scheduling {context} shutdown in {rd_delay}s");
tokio::time::sleep(Duration::from_secs(rd_delay)).await;
if let Err(e) = tx.send(()) {
tracing::error!(error = %e, "Could not send killpill for {context}");
}
}
+2 -1
View File
@@ -1094,7 +1094,7 @@ pub async fn reload_option_setting_with_tracing<T: FromStr + DeserializeOwned>(
}
}
async fn load_value_from_global_settings(
pub async fn load_value_from_global_settings(
db: &DB,
setting_name: &str,
) -> error::Result<Option<serde_json::Value>> {
@@ -1107,6 +1107,7 @@ async fn load_value_from_global_settings(
.map(|x| x.value);
Ok(r)
}
pub async fn reload_option_setting<T: FromStr + DeserializeOwned>(
db: &DB,
setting_name: &str,
+2 -2
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.455.1
version: 1.456.0
title: Windmill API
contact:
@@ -4475,7 +4475,7 @@ paths:
/w/{workspace}/scripts/delete/p/{path}:
post:
summary: delete all scripts at a given path (require admin)
summary: delete script at a given path (require admin)
operationId: deleteScriptByPath
tags:
- script
+7
View File
@@ -173,6 +173,13 @@ pub async fn migrate(db: &DB) -> Result<(), Error> {
let migrator = db.acquire().await?;
let mut custom_migrator = CustomMigrator { inner: migrator };
if let Err(err) = sqlx::query!("DELETE FROM _sqlx_migrations WHERE version=20250131115248")
.execute(db)
.await
{
tracing::info!("Could not remove sqlx migration with version=20250131115248: {err:#}");
}
match sqlx::migrate!("../migrations")
.run_direct(&mut custom_migrator)
.await
+36 -9
View File
@@ -34,7 +34,11 @@ use http::HeaderValue;
use reqwest::Client;
#[cfg(feature = "oauth2")]
use std::collections::HashMap;
use windmill_common::global_settings::load_value_from_global_settings;
use windmill_common::global_settings::EMAIL_DOMAIN_SETTING;
use windmill_common::worker::HUB_CACHE_DIR;
use std::fs::DirBuilder;
use std::time::Duration;
use std::{net::SocketAddr, sync::Arc};
use tokio::sync::RwLock;
@@ -203,6 +207,13 @@ pub async fn run_server(
) -> anyhow::Result<()> {
let user_db = UserDB::new(db.clone());
for x in [HUB_CACHE_DIR] {
DirBuilder::new()
.recursive(true)
.create(x)
.expect("could not create initial server dir");
}
#[cfg(feature = "enterprise")]
let ext_jwks = ExternalJwks::load().await;
let auth_cache = Arc::new(crate::auth::AuthCache::new(
@@ -242,16 +253,32 @@ pub async fn run_server(
#[cfg(feature = "embedding")]
load_embeddings_db(&db);
#[cfg(feature = "smtp")]
let mut start_smtp_server = false;
if let Some(smtp_settings) =
load_value_from_global_settings(&db, EMAIL_DOMAIN_SETTING).await?
{
let smtp_server = Arc::new(SmtpServer {
db: db.clone(),
user_db: user_db,
auth_cache: auth_cache.clone(),
base_internal_url: base_internal_url.clone(),
});
if let Err(err) = smtp_server.start_listener_thread(addr).await {
tracing::error!("Error starting SMTP server: {err:#}");
if smtp_settings.as_str().unwrap_or("") != "" {
start_smtp_server = true;
}
}
if !start_smtp_server {
tracing::info!("SMTP server not started because email domain is not set");
} else {
#[cfg(feature = "smtp")]
{
let smtp_server = Arc::new(SmtpServer {
db: db.clone(),
user_db: user_db,
auth_cache: auth_cache.clone(),
base_internal_url: base_internal_url.clone(),
});
if let Err(err) = smtp_server.start_listener_thread(addr).await {
tracing::error!("Error starting SMTP server: {err:#}");
}
}
#[cfg(not(feature = "smtp"))]
{
tracing::info!("SMTP server not started because SMTP feature is not enabled");
}
}
}
+11 -6
View File
@@ -10,6 +10,7 @@
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;
use crate::db::ApiAuthed;
@@ -464,9 +465,11 @@ async fn list_user_usage(
require_admin(authed.is_admin, &authed.username)?;
}
let mut tx = user_db.begin(&authed).await?;
let rows = sqlx::query_as!(
UserWithUsage,
"
let rows = tokio::time::timeout(
Duration::from_secs(300),
sqlx::query_as!(
UserWithUsage,
"
SELECT usr.email, usage.executions
FROM usr
, LATERAL (
@@ -479,10 +482,12 @@ async fn list_user_usage(
) usage
WHERE workspace_id = $1
",
w_id
w_id
)
.fetch_all(&mut *tx),
)
.fetch_all(&mut *tx)
.await?;
.await
.map_err(|e| Error::InternalErr(format!("Timed out while fetching user usage: {e:#}")))??;
tx.commit().await?;
Ok(Json(rows))
}
+6 -3
View File
@@ -469,12 +469,15 @@ pub async fn get_full_hub_script_by_path(
let script;
if tokio::fs::metadata(&cache_path).await.is_err() {
script = get_full_hub_script_by_path_inner(path, http_client, db).await?;
crate::worker::write_file(
if let Err(e) = crate::worker::write_file(
HUB_CACHE_DIR,
&version,
&serde_json::to_string(&script).map_err(to_anyhow)?,
)?;
tracing::info!("wrote hub script {path} to cache");
) {
tracing::error!("failed to write hub script {path} to cache: {e}");
} else {
tracing::info!("wrote hub script {path} to cache");
}
} else {
let cache_content = tokio::fs::read_to_string(cache_path).await?;
script = serde_json::from_str(&cache_content).unwrap();
+2
View File
@@ -171,6 +171,8 @@ pub async fn make_pull_query(wc: &WorkerConfig) {
}
pub const TMP_DIR: &str = "/tmp/windmill";
pub const TMP_LOGS_DIR: &str = concatcp!(TMP_DIR, "/logs");
pub const HUB_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "hub");
pub const ROOT_CACHE_DIR: &str = concatcp!(TMP_DIR, "/cache/");
+5 -2
View File
@@ -13,6 +13,9 @@ use windmill_common::error;
#[cfg(all(feature = "enterprise", feature = "parquet", unix))]
use std::sync::Arc;
#[cfg(all(feature = "enterprise", feature = "parquet"))]
pub const TARGET: &str = const_format::concatcp!(std::env::consts::OS, "_", std::env::consts::ARCH);
#[cfg(all(feature = "enterprise", feature = "parquet", unix))]
pub async fn build_tar_and_push(
s3_client: Arc<dyn ObjectStore>,
@@ -57,7 +60,7 @@ pub async fn build_tar_and_push(
if let Err(e) = s3_client
.put(
&Path::from(format!(
"/tar/{}/{folder_name}.tar",
"/tar/{TARGET}/{}/{folder_name}.tar",
if no_uv { "pip" } else { &python_xyz }
)),
std::fs::read(&tar_path)?.into(),
@@ -100,7 +103,7 @@ pub async fn pull_from_tar(
let start = Instant::now();
let tar_path = format!(
"tar/{}/{folder_name}.tar",
"tar/{TARGET}/{}/{folder_name}.tar",
if no_uv { "pip".to_owned() } else { python_xyz }
);
let bytes = attempt_fetch_bytes(client, &tar_path).await?;
@@ -632,7 +632,7 @@ pub async fn uv_pip_compile(
if *NATIVE_CERT {
args.extend(["--native-tls"]);
}
tracing::error!("uv args: {:?}", args);
tracing::debug!("uv args: {:?}", args);
#[cfg(windows)]
let uv_cmd = "uv";
-2
View File
@@ -262,8 +262,6 @@ pub async fn create_token_for_owner(
Ok(format!("jwt_{}", token))
}
pub const TMP_LOGS_DIR: &str = concatcp!(TMP_DIR, "/logs");
pub const ROOT_CACHE_NOMOUNT_DIR: &str = concatcp!(TMP_DIR, "/cache_nomount/");
pub const LOCK_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "lock");
+1 -1
View File
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.455.1";
export const VERSION = "v1.456.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+1 -1
View File
@@ -60,7 +60,7 @@ export {
// }
// });
export const VERSION = "1.455.1";
export const VERSION = "1.456.0";
const command = new Command()
.name("wmill")
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.455.1",
"version": "1.456.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.455.1",
"version": "1.456.0",
"license": "AGPL-3.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.455.1",
"version": "1.456.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -233,7 +233,7 @@
$: !!editTab ? openEditTab() : closeEditTab()
let pannelButtonWidth: number = 0
let panelButtonWidth: number = 0
export let pannelExtraButtonWidth: number = 0
export function updateJson() {
@@ -246,8 +246,8 @@
<div class="relative z-[100000]">
<div
class="absolute"
style="right: calc({editPanelSize}% - 2px - {pannelExtraButtonWidth}px); top: 0px;"
bind:clientWidth={pannelButtonWidth}
style="right: calc({editPanelSize}% - 1px - {pannelExtraButtonWidth}px); top: 0px;"
bind:clientWidth={panelButtonWidth}
>
<slot name="openEditTab" />
</div>
@@ -265,7 +265,7 @@
<div
style={editPanelSize > 0
? `width: 100%;`
: `width: calc(100% - ${pannelButtonWidth - pannelExtraButtonWidth}px);`}
: `width: calc(100% - ${panelButtonWidth - pannelExtraButtonWidth}px);`}
>
<slot name="addProperty" />
</div>
@@ -317,7 +317,7 @@
<Pane
bind:size={editPanelSize}
minSize={noPreview ? 100 : 50}
class={twMerge('border rounded-md', pannelButtonWidth > 0 ? 'rounded-tl-none' : '')}
class={twMerge('border rounded-md', panelButtonWidth > 0 ? 'rounded-tl-none' : '')}
>
{#if editTab !== 'inputEditor'}
<slot name="extraTab" />
@@ -1446,6 +1446,7 @@
showCaptureHint.set(true)
}}
bind:this={flowPreviewButtons}
{loading}
/>
<Button
loading={loadingDraft}
@@ -0,0 +1,49 @@
<script lang="ts">
import { HistoryIcon } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import PopoverV2 from '$lib/components/meltComponents/Popover.svelte'
import HistoricInputs from './HistoricInputs.svelte'
import { workspaceStore } from '$lib/stores'
import { JobService } from '$lib/gen'
export let path: string
export let selected: string | undefined = undefined
const dispatch = createEventDispatcher()
async function loadInitial() {
let jobs = await JobService.listJobs({
workspace: $workspaceStore!,
scriptPathExact: path,
jobKinds: ['flow', 'flowpreview'].join(','),
page: 1,
perPage: 1
})
if (jobs.length > 0) {
dispatch('select', { jobId: jobs[0].id, initial: true })
}
}
$: $workspaceStore && loadInitial()
</script>
<PopoverV2 closeButton={false}>
<svelte:fragment slot="trigger">
<HistoryIcon size={14} />
</svelte:fragment>
<svelte:fragment slot="content">
<div class="p-2 h-[400px] overflow-hidden w-80 border shadow-sm">
<HistoricInputs
on:select={(e) => {
if (e.detail) {
dispatch('select', { jobId: e.detail?.jobId, initial: false })
} else {
dispatch('unselect')
}
}}
{selected}
runnableId={path}
runnableType={'FlowPath'}
/>
</div>
</svelte:fragment>
</PopoverV2>
@@ -16,6 +16,7 @@
import InputSelectedBadge from './schema/InputSelectedBadge.svelte'
import Toggle from './Toggle.svelte'
import JsonInputs from './JsonInputs.svelte'
import FlowHistoryJobPicker from './FlowHistoryJobPicker.svelte'
export let previewMode: 'upTo' | 'whole'
export let open: boolean
@@ -54,6 +55,11 @@
} = getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
let renderCount: number = 0
let initial: boolean = false
let schemaFormWithArgPicker: SchemaFormWithArgPicker | undefined = undefined
let currentJobId: string | undefined = undefined
function extractFlow(previewMode: 'upTo' | 'whole'): OpenFlow {
if (previewMode === 'whole') {
return $flowStore
@@ -74,6 +80,9 @@
args: Record<string, any>,
restartedFrom: RestartedFrom | undefined
) {
if (initial) {
initial = false
}
try {
lastPreviewFlow = JSON.stringify($flowStore)
jobProgressReset()
@@ -89,6 +98,7 @@
isRunning = false
jobId = undefined
}
schemaFormWithArgPicker?.refreshHistory()
}
function onKeyDown(event: KeyboardEvent) {
@@ -159,8 +169,6 @@
}
$: selectedJobStep !== undefined && onSelectedJobStepChange()
let renderCount: number = 0
</script>
<svelte:window on:keydown={onKeyDown} />
@@ -324,6 +332,7 @@
<div class="overflow-y-auto grow flex flex-col pt-4">
<div class="border-b">
<SchemaFormWithArgPicker
bind:this={schemaFormWithArgPicker}
runnableId={initialPath == '' ? $pathStore : initialPath}
runnableType={'FlowPath'}
previewArgs={$previewArgs}
@@ -335,7 +344,10 @@
{jsonView}
>
<div class="w-full flex flex-row justify-between">
<InputSelectedBadge {inputSelected} />
<InputSelectedBadge
on:click={() => schemaFormWithArgPicker?.resetSelected()}
{inputSelected}
/>
<div class="flex flex-row gap-2">
<Toggle
bind:checked={jsonView}
@@ -384,8 +396,43 @@
{/if}
</SchemaFormWithArgPicker>
</div>
<div class="pt-4 flex flex-col grow">
<div class="pt-4 flex flex-col grow relative">
<div
class="absolute top-[22px] right-2 border p-1.5 hover:bg-surface-hover rounded-md center-center"
>
<FlowHistoryJobPicker
on:select={(e) => {
if (!currentJobId) {
currentJobId = jobId
}
const detail = e.detail
initial = detail.initial
jobId = detail.jobId
}}
on:unselect={() => {
jobId = currentJobId
currentJobId = undefined
}}
path={initialPath == '' ? $pathStore : initialPath}
/>
</div>
{#if jobId}
{#if initial}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
on:click={() => {
initial = false
}}
class="cursor-pointer h-full hover:bg-gray-500/20 dark:hover:bg-gray-500/20 dark:bg-gray-500/80 rounded bg-gray-500/40 absolute top-0 left-0 w-full z-50"
>
<div class="text-center text-primary text-lg py-2 pt-20"
><span class="font-bold border p-2 bg-surface-secondary rounded-md"
>Previous run of this flow from history</span
></div
>
</div>
{/if}
<FlowStatusViewer
hideDownloadInGraph={customUi?.downloadLogs === false}
wideResults
@@ -933,6 +933,8 @@
<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
</Tabs>
{:else}
<div class="h-[30px]" />
{/if}
{/if}
<div class="{selected != 'sequence' ? 'hidden' : ''} max-w-7xl mx-auto">
+25 -112
View File
@@ -1,66 +1,23 @@
<script lang="ts">
import { InputService, type RunnableType, type Job } from '$lib/gen/index.js'
import { workspaceStore } from '$lib/stores.js'
import { type RunnableType, type Job } from '$lib/gen/index.js'
import { sendUserToast } from '$lib/utils.js'
import JobSchemaPicker from '$lib/components/schema/JobSchemaPicker.svelte'
import RunningJobSchemaPicker from '$lib/components/schema/RunningJobSchemaPicker.svelte'
import { createEventDispatcher, onDestroy } from 'svelte'
import JobLoader from './runs/JobLoader.svelte'
import { DataTable } from '$lib/components/table'
import { clickOutside } from '$lib/utils'
import InfiniteList from './InfiniteList.svelte'
import HistoricList from './HistoricList.svelte'
export let runnableId: string | undefined = undefined
export let runnableType: RunnableType | undefined = undefined
export let loading: boolean = false
export let selected: string | undefined = undefined
let historicList: HistoricList | undefined = undefined
const dispatch = createEventDispatcher()
let jobs: Job[] = []
let hasMoreCurrentRuns = false
let page = 1
let infiniteList: InfiniteList | undefined = undefined
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
let cachedArgs: Record<string, any> = {}
function initLoadInputs() {
loadInputsPageFn = async (page: number, perPage: number) => {
const inputs = await InputService.getInputHistory({
workspace: $workspaceStore!,
runnableId,
runnableType,
page,
perPage,
includePreview: true
})
const inputsWithPayload = await Promise.all(
inputs.map(async (input) => {
if (cachedArgs[input.id]) {
return {
...input,
payloadData: cachedArgs[input.id]
}
}
const payloadData = await loadArgsFromHistory(input.id, undefined, false)
if (payloadData === 'WINDMILL_TOO_BIG') {
return {
...input,
payloadData: 'WINDMILL_TOO_BIG',
getFullPayload: () => loadArgsFromHistory(input.id, undefined, true)
}
}
cachedArgs[input.id] = payloadData
return {
...input,
payloadData
}
})
)
return inputsWithPayload
}
infiniteList?.setLoader(loadInputsPageFn)
}
async function handleSelected(data: any) {
if (selected === data.id) {
@@ -70,39 +27,16 @@
selected = data.id
if (data.payloadData === 'WINDMILL_TOO_BIG') {
const fullPayload = await data.getFullPayload?.()
dispatch('select', fullPayload)
dispatch('select', { args: fullPayload, jobId: data.id })
} else {
dispatch('select', structuredClone(data.payloadData))
dispatch('select', { args: structuredClone(data.payloadData), jobId: data.id })
}
}
let selected: string | undefined = undefined
onDestroy(() => {
resetSelected(true)
})
async function getPropPickerElements(): Promise<HTMLElement[]> {
return Array.from(
document.querySelectorAll('[data-schema-picker], [data-schema-picker] *')
) as HTMLElement[]
}
async function loadArgsFromHistory(
id: string | undefined,
input: boolean | undefined,
allowLarge: boolean
): Promise<any> {
if (!id) return
const payloadData = await InputService.getArgsFromHistoryOrSavedInput({
jobOrInputId: id,
workspace: $workspaceStore!,
input,
allowLarge
})
return payloadData
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && selected) {
resetSelected(true)
@@ -120,9 +54,7 @@
let jobHovered: string | undefined = undefined
export function refresh() {
if (infiniteList) {
infiniteList.loadData('refresh')
}
historicList?.refresh()
}
export function resetSelected(dispatchEvent?: boolean) {
@@ -132,8 +64,16 @@
}
}
$: !loading && refresh()
$: $workspaceStore && runnableId && runnableType && infiniteList && initLoadInputs()
function getJobKinds(runnableType: RunnableType | undefined) {
if (runnableType === 'FlowPath') {
return 'flow,flowpreview'
} else if (runnableType === 'ScriptPath') {
return 'script,preview'
} else if (runnableType === 'ScriptHash') {
return 'script,preview'
}
return 'all'
}
</script>
<svelte:window on:keydown={handleKeydown} />
@@ -143,7 +83,7 @@
bind:jobs
path={runnableId}
isSkipped={false}
jobKindsCat="all"
jobKinds={getJobKinds(runnableType)}
user={null}
label={null}
folder={null}
@@ -159,15 +99,7 @@
/>
{/if}
<div
class="h-full w-full flex flex-col gap-4"
use:clickOutside={{ capture: false, exclude: getPropPickerElements }}
on:click_outside={() => {
if (selected) {
resetSelected(true)
}
}}
>
<div class="h-full max-h-full min-h-0 w-full flex flex-col gap-4">
<div class="grow-0" data-schema-picker>
<DataTable size="xs" bind:currentPage={page} hasMore={hasMoreCurrentRuns} tableFixed={true}>
{#if loading && (jobs == undefined || jobs?.length == 0)}
@@ -201,32 +133,13 @@
</div>
<div class="min-h-0 grow" data-schema-picker>
<InfiniteList
bind:this={infiniteList}
selectedItemId={selected}
<HistoricList
bind:this={historicList}
on:error={(e) => handleError(e.detail)}
on:select={(e) => handleSelected(e.detail)}
>
<svelte:fragment slot="columns">
<colgroup>
<col class="w-8" />
<col class="w-16" />
<col />
</colgroup>
</svelte:fragment>
<svelte:fragment let:item let:hover>
<JobSchemaPicker
job={item}
selected={selected === item.id}
hovering={hover}
payloadData={item.payloadData}
/>
</svelte:fragment>
<svelte:fragment slot="empty">
<div class="text-center text-tertiary text-xs py-2">
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
</div>
</svelte:fragment>
</InfiniteList>
{runnableId}
{runnableType}
{selected}
/>
</div>
</div>
@@ -0,0 +1,109 @@
<script lang="ts">
import { InputService, type RunnableType } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { onDestroy } from 'svelte'
import InfiniteList from './InfiniteList.svelte'
import JobSchemaPicker from './schema/JobSchemaPicker.svelte'
export let runnableId: string | undefined
export let runnableType: RunnableType | undefined
export let selected: string | undefined = undefined
let infiniteList: InfiniteList | undefined = undefined
let loadInputsPageFn: ((page: number, perPage: number) => Promise<any>) | undefined = undefined
export function refresh() {
if (infiniteList) {
infiniteList.loadData('refresh')
}
}
let cachedArgs: Record<string, any> = {}
let interval: NodeJS.Timeout | undefined = undefined
function initLoadInputs() {
interval && clearInterval(interval)
interval = setInterval(() => {
refresh()
}, 10000)
loadInputsPageFn = async (page: number, perPage: number) => {
const inputs = await InputService.getInputHistory({
workspace: $workspaceStore!,
runnableId,
runnableType,
page,
perPage,
includePreview: true
})
const inputsWithPayload = await Promise.all(
inputs.map(async (input) => {
if (cachedArgs[input.id]) {
return {
...input,
payloadData: cachedArgs[input.id]
}
}
const payloadData = await loadArgsFromHistory(input.id, undefined, false)
if (payloadData === 'WINDMILL_TOO_BIG') {
return {
...input,
payloadData: 'WINDMILL_TOO_BIG',
getFullPayload: () => loadArgsFromHistory(input.id, undefined, true)
}
}
cachedArgs[input.id] = payloadData
return {
...input,
payloadData
}
})
)
return inputsWithPayload
}
infiniteList?.setLoader(loadInputsPageFn)
}
onDestroy(() => {
interval && clearInterval(interval)
})
async function loadArgsFromHistory(
id: string | undefined,
input: boolean | undefined,
allowLarge: boolean
): Promise<any> {
if (!id) return
const payloadData = await InputService.getArgsFromHistoryOrSavedInput({
jobOrInputId: id,
workspace: $workspaceStore!,
input,
allowLarge
})
return payloadData
}
$: $workspaceStore && runnableId && runnableType && infiniteList && initLoadInputs()
</script>
<InfiniteList bind:this={infiniteList} selectedItemId={selected} on:error on:select>
<svelte:fragment slot="columns">
<colgroup>
<col class="w-8" />
<col class="w-16" />
<col />
</colgroup>
</svelte:fragment>
<svelte:fragment let:item let:hover>
<JobSchemaPicker
job={item}
selected={selected === item.id}
hovering={hover}
payloadData={item.payloadData}
/>
</svelte:fragment>
<svelte:fragment slot="empty">
<div class="text-center text-tertiary text-xs py-2">
{runnableId ? 'No previous inputs' : 'Save draft to see previous runs'}
</div>
</svelte:fragment>
</InfiniteList>
@@ -22,9 +22,11 @@
let deleteItemFn: ((id: any) => Promise<any>) | undefined = undefined
export async function loadData(loadOption: 'refresh' | 'forceRefresh' | 'loadMore' = 'loadMore') {
// console.log('loadData', loadOption, length, items?.length)
if (!loadInputs) return
loading = true
hasMore = hasMore
hasMore = length === perPage * page
if (hasMore && loadOption === 'loadMore') {
page++
@@ -160,7 +162,7 @@
@keyframes greenHighlight {
0% {
background-color: rgba(70, 255, 138, 0.5);
background-color: rgba(70, 255, 138, 0.4);
box-shadow: 0 0 15px rgb(34 197 94 / 0.3);
}
100% {
@@ -170,7 +172,7 @@
}
:global(.animate-slideIn) {
animation: greenHighlight 2s ease-out forwards;
animation: greenHighlight 1s ease-out forwards;
will-change: transform, opacity, background-color, box-shadow;
position: relative;
}
@@ -7,7 +7,6 @@
import { Edit, Trash2, Save, Eye } from 'lucide-svelte'
import Toggle from './Toggle.svelte'
import { Cell } from './table/index'
import { clickOutside } from '$lib/utils'
import SaveInputsButton from '$lib/components/SaveInputsButton.svelte'
import Popover from '$lib/components/Popover.svelte'
import InfiniteList from './InfiniteList.svelte'
@@ -133,11 +132,6 @@
resetSelected(true)
})
async function getPropPickerElements(): Promise<HTMLElement[]> {
const elements = document.querySelectorAll('[data-schema-picker], [data-schema-picker] *')
return elements ? (Array.from(elements) as HTMLElement[]) : []
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && isEditing) {
setEditing(null)
@@ -191,13 +185,7 @@
<svelte:window on:keydown={handleKeydown} />
<div
class="w-full flex flex-col gap-1 h-full overflow-y-auto"
use:clickOutside={{ capture: false, exclude: getPropPickerElements }}
on:click_outside={() => {
resetSelected(true)
}}
>
<div class="w-full flex flex-col gap-1 h-full overflow-y-auto">
{#if !noButton}
<div>
<Popover class="w-full" placement="bottom" disablePopup={runnableId && previewArgs}>
@@ -18,6 +18,11 @@
export let isValid: boolean
export let args: object
export function resetSelected() {
historicInputs?.resetSelected(true)
savedInputsPicker?.resetSelected(true)
}
let savedArgs: any = undefined
let runnableType: RunnableType | undefined = undefined
let savedInputsPicker: SavedInputsPicker | undefined = undefined
@@ -90,7 +95,7 @@
{runnableType}
on:select={(e) => {
if (e.detail) savedInputsPicker?.resetSelected()
selectArgs(e.detail, e.detail ? 'history' : undefined)
selectArgs(e.detail?.args, e.detail ? 'history' : undefined)
}}
/>
</Section>
@@ -19,6 +19,16 @@
const dispatch = createEventDispatcher()
export function refreshHistory() {
historicInputs?.refresh()
}
export function resetSelected() {
historicInputs?.resetSelected(true)
savedInputsPicker?.resetSelected(true)
captureTable?.resetSelected(true)
}
const getDropdownItems = () => {
return [
{
@@ -71,23 +81,24 @@
let dropdownItems: any
let rightPanelOpen = false
let savedInputsPicker: SavedInputsPicker | undefined = undefined
let loading = false
let captureTable: CaptureTable | undefined = undefined
let historicInputs: HistoricInputs | undefined = undefined
$: selectedTab, (dropdownItems = getDropdownItems())
let inputPanelSize = 70
</script>
<div class="h-fit">
<div class="relative z-[100000] w-full">
<div class="absolute" style="right: calc({100 - inputPanelSize}%); top: -1px;">
<SideBarTab expandRight={rightPanelOpen} {dropdownItems} fullMenu={true} noTrigger={true} />
</div>
</div>
<Splitpanes class={!rightPanelOpen ? 'splitter-hidden' : ''}>
<Pane class="!overflow-visible" size={70} minSize={30}>
<div class="relative w-full h-fit pr-12 pb-4 overflow-hidden" bind:clientHeight={rightHeight}>
<div class="absolute right-[1px] -top-[1px] z-50">
<SideBarTab
expandRight={rightPanelOpen}
{dropdownItems}
fullMenu={true}
noTrigger={true}
/>
</div>
<Pane bind:size={inputPanelSize} class="!overflow-visible" minSize={30}>
<div class="relative w-full h-fit pr-12 pb-4" bind:clientHeight={rightHeight}>
<slot />
</div>
</Pane>
@@ -108,7 +119,7 @@
{runnableId}
{runnableType}
on:select={(e) => {
dispatch('select', { payload: e.detail, type: 'history' })
dispatch('select', { payload: e.detail?.args, type: 'history' })
}}
/>
</FlowInputEditor>
@@ -119,6 +130,7 @@
{runnableId}
{runnableType}
{previewArgs}
bind:this={savedInputsPicker}
on:select={(e) => {
dispatch('select', { payload: e.detail, type: 'saved' })
}}
@@ -138,6 +150,7 @@
on:select={(e) => {
dispatch('select', { payload: e.detail, type: 'captures' })
}}
bind:this={captureTable}
isFlow={true}
headless={true}
addButton={false}
+15 -7
View File
@@ -1,8 +1,9 @@
<script lang="ts">
import { enterpriseLicense } from '$lib/stores'
import { AlertTriangle, ChevronDown, ChevronRight } from 'lucide-svelte'
import { AlertTriangle, ChevronRight } from 'lucide-svelte'
import Tooltip from './Tooltip.svelte'
import { twMerge } from 'tailwind-merge'
import { slide } from 'svelte/transition'
export let label: string | undefined = undefined
export let tooltip: string | undefined = undefined
@@ -14,6 +15,7 @@
export let collapsable: boolean = false
export let collapsed: boolean = true
export let headless: boolean = false
export let animate: boolean = false
</script>
<div class={twMerge('w-full flex flex-col', wrapperClass)}>
@@ -27,11 +29,14 @@
>
{#if collapsable}
<button class="flex items-center gap-1" on:click={() => (collapsed = !collapsed)}>
{#if collapsed}
<ChevronRight size={16} />
{:else}
<ChevronDown size={16} />
{/if}
<ChevronRight
size={16}
class={twMerge(
'transition',
collapsed ? '' : 'rotate-90',
animate ? 'duration-200' : 'duration-0'
)}
/>
{label}
</button>
{:else}
@@ -58,7 +63,10 @@
</div>
{/if}
{#if !collapsable || !collapsed}
<div class={twMerge('grow min-h-0', $$props.class)}>
<div
class={twMerge('grow min-h-0', $$props.class)}
transition:slide={animate ? { duration: 200 } : { duration: 0 }}
>
<slot />
</div>
{/if}
@@ -74,6 +74,7 @@
on:updateSchema
on:testWithArgs
{newItem}
alwaysOpened={true}
/>
{:else}
<div>
@@ -308,6 +308,7 @@
savedPreviewArgs = undefined
}
}
let historicInputs: HistoricInputs | undefined = undefined
</script>
<!-- Add svelte:window to listen for keyboard events -->
@@ -348,6 +349,7 @@
{diff}
disableDnd={!!previewSchema}
on:rejectChange={(e) => {
console.log('rejectChange')
rejectChange(e.detail).then(() => {
updatePreviewSchema(selectedSchema)
})
@@ -422,7 +424,9 @@
size="xs"
startIcon={{ icon: X }}
shortCut={{ key: 'esc', withoutModifier: true }}
nonCaptureEvent
on:click={() => {
historicInputs?.resetSelected(true)
}}
/>
</div>
{:else}
@@ -460,10 +464,11 @@
}}
>
<HistoricInputs
bind:this={historicInputs}
runnableId={initialPath ?? undefined}
runnableType={$pathStore ? 'FlowPath' : undefined}
on:select={(e) => {
updatePreviewSchemaAndArgs(e.detail ?? undefined)
updatePreviewSchemaAndArgs(e.detail?.args ?? undefined)
}}
/>
</FlowInputEditor>
@@ -10,6 +10,9 @@
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import { Play } from 'lucide-svelte'
export let loading = false
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
let previewOpen = false
let previewMode: 'upTo' | 'whole' = 'whole'
@@ -91,20 +94,22 @@
Test flow
</Button>
<Drawer bind:open={previewOpen} alwaysOpen size="75%" {preventEscape}>
<FlowPreviewContent
bind:this={flowPreviewContent}
open={previewOpen}
bind:previewMode
bind:job
bind:jobId
on:close={() => {
previewOpen = false
}}
on:openTriggers={(e) => {
previewOpen = false
dispatch('openTriggers', e.detail)
}}
bind:preventEscape
/>
</Drawer>
{#if !loading}
<Drawer bind:open={previewOpen} alwaysOpen size="75%" {preventEscape}>
<FlowPreviewContent
bind:this={flowPreviewContent}
open={previewOpen}
bind:previewMode
bind:job
bind:jobId
on:close={() => {
previewOpen = false
}}
on:openTriggers={(e) => {
previewOpen = false
dispatch('openTriggers', e.detail)
}}
bind:preventEscape
/>
</Drawer>
{/if}
@@ -35,7 +35,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="flex flex-col relative !overflow-visible {expandRight ? 'pr-2 -mr-2' : ''}"
class="flex flex-col relative !overflow-visible w-[33px]"
on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave}
>
@@ -6,6 +6,7 @@
import { ExternalLinkIcon } from 'lucide-svelte'
import type { FlowStatusViewerContext } from '../graph'
import { getContext } from 'svelte'
import { truncateRev } from '$lib/utils'
export let job: QueuedJob | CompletedJob
let { hideJobId } = getContext<FlowStatusViewerContext>('FlowStatusViewer')
@@ -22,7 +23,7 @@
target="_blank"
href="{base}/run/{job?.id}?workspace={job?.workspace_id}"
>
{job?.id}
{truncateRev(job?.id, 8)}
<ExternalLinkIcon size={14} class="inline mb-1 ml-1" />
</a>
</div>
@@ -28,7 +28,7 @@
export let argFilter: string | undefined
export let resultFilter: string | undefined = undefined
export let schedulePath: string | undefined = undefined
export let jobKindsCat: string
export let jobKindsCat: string | undefined = undefined
export let minTs: string | undefined = undefined
export let maxTs: string | undefined = undefined
export let jobKinds: string = ''
@@ -91,6 +91,9 @@
}
function computeJobKinds(jobKindsCat: string | undefined): string {
if (jobKindsCat == undefined && jobKinds != undefined) {
return jobKinds
}
if (jobKindsCat == 'all') {
let kinds: CompletedJob['job_kind'][] = [
'script',
@@ -103,7 +106,7 @@
'script_hub',
'flowscript',
'flownode',
'appscript',
'appscript'
]
return kinds.join(',')
} else if (jobKindsCat == 'dependencies') {
@@ -120,7 +123,13 @@
let kinds: CompletedJob['job_kind'][] = ['deploymentcallback']
return kinds.join(',')
} else {
let kinds: CompletedJob['job_kind'][] = ['script', 'flow', 'flowscript', 'flownode', 'appscript']
let kinds: CompletedJob['job_kind'][] = [
'script',
'flow',
'flowscript',
'flownode',
'appscript'
]
return kinds.join(',')
}
}
@@ -7,7 +7,7 @@
export let inputSelected: 'history' | 'captures' | 'saved' | undefined = undefined
</script>
<div class="min-h-[34px]">
<div class="min-h-[38px]">
<div
class="rounded-md flex flex-row gap-2 items-center py-1 px-2 w-fit {classes['info']
.bgClass} {inputSelected ? '' : 'hidden'}"
@@ -24,7 +24,7 @@
size="xs2"
startIcon={{ icon: X }}
shortCut={{ key: 'esc', withoutModifier: true }}
nonCaptureEvent
on:click
/>
</div>
</div>
@@ -21,23 +21,26 @@
export let canHavePreprocessor: boolean = false
export let hasPreprocessor: boolean = false
export let newItem: boolean
export let openForm: boolean = false
export let alwaysOpened: boolean = false
export let showCapture: boolean = false
const captureTypeLabels: Record<CaptureTriggerKind, string> = {
http: 'New custom HTTP route',
websocket: 'New WebSocket trigger',
http: '+ New custom HTTP route',
websocket: '+ New WebSocket trigger',
webhook: 'Webhook',
kafka: 'New Kafka trigger',
kafka: '+ New Kafka trigger',
email: 'Email trigger',
nats: 'NATS trigger'
nats: '+ New NATS trigger'
}
const { captureOn } = getContext<TriggerContext>('TriggerContext')
let args: Record<string, any> = {}
$: collapsed = !openForm
const dispatch = createEventDispatcher()
let showCapture = false
let init = false
$: updateShowCapture(!!$captureOn)
function updateShowCapture(show: boolean) {
@@ -49,55 +52,57 @@
}
</script>
<Section label={captureTypeLabels[triggerType]}>
<Section label={captureTypeLabels[triggerType]} collapsable={!alwaysOpened} bind:collapsed>
<svelte:fragment slot="action">
<div class="flex flex-row grow w-min-0 gap-2 items-center justify-end">
{#if isEditor}
<Button
size="xs2"
on:click={() => {
showCapture = !showCapture
}}
variant="border"
color="light"
endIcon={{
icon: ChevronDown,
classes: twMerge('transition', showCapture ? 'rotate-180' : '')
}}
>
Test trigger
</Button>
{/if}
{#if !noSave}
{@const disabled = newItem || cloudDisabled}
<Popover notClickable>
{#if !collapsed || alwaysOpened}
<div class="flex flex-row grow w-min-0 gap-2 items-center justify-end">
{#if isEditor}
<Button
size="xs2"
{disabled}
startIcon={{ icon: Save }}
on:click={() => {
dispatch('saveTrigger', {
config: args
})
showCapture = !showCapture
}}
variant="border"
color="light"
endIcon={{
icon: ChevronDown,
classes: twMerge('transition', showCapture ? 'rotate-180' : '')
}}
>
Save
Test trigger
</Button>
<svelte:fragment slot="text">
{#if disabled}
{#if newItem}
Deploy the runnable to enable trigger creation
{:else if cloudDisabled}
{capitalize(triggerType)} triggers are disabled in the multi-tenant cloud
{/if}
{#if !noSave}
{@const disabled = newItem || cloudDisabled}
<Popover notClickable>
<Button
size="xs2"
{disabled}
startIcon={{ icon: Save }}
on:click={() => {
dispatch('saveTrigger', {
config: args
})
}}
>
Save
</Button>
<svelte:fragment slot="text">
{#if disabled}
{#if newItem}
Deploy the runnable to enable trigger creation
{:else if cloudDisabled}
{capitalize(triggerType)} triggers are disabled in the multi-tenant cloud
{/if}
{:else}
Create new {captureTypeLabels[triggerType].toLowerCase()}
{/if}
{:else}
Create new {captureTypeLabels[triggerType].toLowerCase()}
{/if}
</svelte:fragment>
</Popover>
{/if}
</div>
</svelte:fragment>
</Popover>
{/if}
</div>
{/if}
</svelte:fragment>
{#if isEditor}
@@ -5,7 +5,7 @@
import { canWrite } from '$lib/utils'
import { getContext, onMount } from 'svelte'
import Section from '$lib/components/Section.svelte'
import { Alert, Skeleton } from '$lib/components/common'
import { Alert } from '$lib/components/common'
import Description from '$lib/components/Description.svelte'
import type { TriggerContext } from '$lib/components/triggers'
import TriggersEditorSection from '../TriggersEditorSection.svelte'
@@ -19,6 +19,8 @@
export let args: Record<string, any> = {}
let routeEditor: RouteEditor
let openForm = true
let dontCloseOnLoad = false
$: path && loadTriggers()
const { triggersCount, selectedTrigger, defaultValues } =
@@ -48,6 +50,7 @@
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
})
$triggersCount = { ...($triggersCount ?? {}), http_routes_count: httpTriggers?.length }
openForm = httpTriggers?.length === 0 || dontCloseOnLoad
} catch (e) {
console.error('impossible to load http routes', e)
}
@@ -61,11 +64,42 @@
bind:this={routeEditor}
/>
<div class="flex flex-col gap-8">
<div class="flex flex-col gap-4">
<Description link="https://www.windmill.dev/docs/core_concepts/http_routing">
Routes expose your scripts and flows as HTTP endpoints. Each route can be configured with a
specific HTTP method and path.
</Description>
{#if !newItem && httpTriggers && httpTriggers.length > 0}
<Section label="Routes">
{#if !$userStore?.is_admin && !$userStore?.is_super_admin}
<Alert title="Only workspace admins can create routes" type="warning" size="xs" />
{/if}
<div class="flex flex-col divide-y pt-2">
{#each httpTriggers as httpTriggers (httpTriggers.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{httpTriggers.path}</div>
<div class="col-span-2 truncate">
{httpTriggers.http_method.toUpperCase()} /{httpTriggers.route_path}
</div>
<div class="flex justify-end">
<button
on:click={() => routeEditor?.openEdit(httpTriggers.path, isFlow)}
class="px-2"
>
{#if httpTriggers.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
</Section>
{/if}
<TriggersEditorSection
on:saveTrigger={(e) => {
routeEditor?.openNew(isFlow, path, e.detail.config)
@@ -74,6 +108,7 @@
on:addPreprocessor
on:updateSchema
on:testWithArgs
bind:showCapture={dontCloseOnLoad}
cloudDisabled={false}
triggerType="http"
{isFlow}
@@ -83,43 +118,6 @@
{hasPreprocessor}
{newItem}
data={{ args }}
bind:openForm
/>
{#if !newItem}
<Section label="Routes">
{#if !$userStore?.is_admin && !$userStore?.is_super_admin}
<Alert title="Only workspace admins can create routes" type="warning" size="xs" />
{/if}
{#if httpTriggers}
{#if httpTriggers.length == 0}
<div class="text-xs text-secondary text-center"> No http routes </div>
{:else}
<div class="flex flex-col divide-y pt-2">
{#each httpTriggers as httpTriggers (httpTriggers.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{httpTriggers.path}</div>
<div class="col-span-2 truncate">
{httpTriggers.http_method.toUpperCase()} /{httpTriggers.route_path}
</div>
<div class="flex justify-end">
<button
on:click={() => routeEditor?.openEdit(httpTriggers.path, isFlow)}
class="px-2"
>
{#if httpTriggers.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
{/if}
{:else}
<Skeleton layout={[[8]]} />
{/if}
</Section>
{/if}
</div>
</div>
@@ -6,7 +6,6 @@
import KafkaTriggerEditor from './KafkaTriggerEditor.svelte'
import { isCloudHosted } from '$lib/cloud'
import Section from '$lib/components/Section.svelte'
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
import Description from '$lib/components/Description.svelte'
import { Alert } from '$lib/components/common'
import type { TriggerContext } from '$lib/components/triggers'
@@ -20,6 +19,8 @@
export let hasPreprocessor: boolean = false
let kafkaTriggerEditor: KafkaTriggerEditor
let openForm = true
let dontCloseOnLoad = false
$: path && loadTriggers()
@@ -50,6 +51,7 @@
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
})
$triggersCount = { ...($triggersCount ?? {}), kafka_count: kafkaTriggers?.length }
openForm = kafkaTriggers?.length === 0 || dontCloseOnLoad
} catch (e) {
console.error('impossible to load Kafka triggers', e)
}
@@ -85,6 +87,32 @@
<Description link="https://www.windmill.dev/docs/core_concepts/kafka_triggers">
Kafka triggers execute scripts and flows in response to messages published to Kafka topics.
</Description>
{#if !newItem && kafkaTriggers && kafkaTriggers.length > 0}
<Section label="Kafka triggers">
<div class="flex flex-col divide-y pt-2">
{#each kafkaTriggers as kafkaTrigger (kafkaTrigger.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{kafkaTrigger.path}</div>
<div class="col-span-2 truncate">
{kafkaTrigger.kafka_resource_path}
</div>
<div class="flex justify-end">
<button
on:click={() => kafkaTriggerEditor?.openEdit(kafkaTrigger.path, isFlow)}
class="px-2"
>
{#if kafkaTrigger.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
</Section>
{/if}
<TriggersEditorSection
on:saveTrigger={(e) => {
saveTrigger(path, e.detail.config)
@@ -102,41 +130,8 @@
{canHavePreprocessor}
{hasPreprocessor}
{newItem}
{openForm}
bind:showCapture={dontCloseOnLoad}
/>
{#if !newItem}
{#if kafkaTriggers}
<Section label="Kafka triggers">
{#if kafkaTriggers.length == 0}
<div class="text-xs text-secondary text-center"> No Kafka triggers </div>
{:else}
<div class="flex flex-col divide-y pt-2">
{#each kafkaTriggers as kafkaTrigger (kafkaTrigger.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{kafkaTrigger.path}</div>
<div class="col-span-2 truncate">
{kafkaTrigger.kafka_resource_path}
</div>
<div class="flex justify-end">
<button
on:click={() => kafkaTriggerEditor?.openEdit(kafkaTrigger.path, isFlow)}
class="px-2"
>
{#if kafkaTrigger.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
{/if}
</Section>
{:else}
<Skeleton layout={[[8]]} />
{/if}
{/if}
</div>
{/if}
@@ -6,7 +6,6 @@
import NatsTriggerEditor from './NatsTriggerEditor.svelte'
import { isCloudHosted } from '$lib/cloud'
import Section from '$lib/components/Section.svelte'
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
import Alert from '$lib/components/common/alert/Alert.svelte'
import Description from '$lib/components/Description.svelte'
import type { TriggerContext } from '$lib/components/triggers'
@@ -20,6 +19,8 @@
export let hasPreprocessor: boolean = false
let natsTriggerEditor: NatsTriggerEditor
let openForm = true
let dontCloseOnLoad = false
$: path && loadTriggers()
@@ -50,6 +51,7 @@
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
})
$triggersCount = { ...($triggersCount ?? {}), nats_count: natsTriggers?.length }
openForm = natsTriggers?.length === 0 || dontCloseOnLoad
} catch (e) {
console.error('impossible to load nats triggers', e)
}
@@ -85,6 +87,34 @@
<Description link="https://www.windmill.dev/docs/core_concepts/nats_triggers">
NATS triggers execute scripts and flows in response to messages published to NATS subjects.
</Description>
{#if !newItem && natsTriggers && natsTriggers.length > 0}
<Section label="NATS Triggers">
<div class="flex flex-col divide-y pt-2">
{#each natsTriggers as natsTrigger (natsTrigger.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{natsTrigger.path}</div>
<div class="col-span-2 truncate">
{natsTrigger.nats_resource_path}
</div>
<div class="flex justify-end">
<button
on:click={() => natsTriggerEditor?.openEdit(natsTrigger.path, isFlow)}
class="px-2"
>
{#if natsTrigger.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
</Section>
{/if}
<TriggersEditorSection
on:saveTrigger={(e) => {
saveTrigger(path, e.detail.config)
@@ -100,41 +130,8 @@
{canHavePreprocessor}
{hasPreprocessor}
{newItem}
{openForm}
bind:showCapture={dontCloseOnLoad}
/>
{#if !newItem}
{#if natsTriggers}
<Section label="NATS Triggers">
{#if natsTriggers.length == 0}
<div class="text-xs text-secondary text-center"> No nats triggers </div>
{:else}
<div class="flex flex-col divide-y pt-2">
{#each natsTriggers as natsTrigger (natsTrigger.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{natsTrigger.path}</div>
<div class="col-span-2 truncate">
{natsTrigger.nats_resource_path}
</div>
<div class="flex justify-end">
<button
on:click={() => natsTriggerEditor?.openEdit(natsTrigger.path, isFlow)}
class="px-2"
>
{#if natsTrigger.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
{/if}
</Section>
{:else}
<Skeleton layout={[[8]]} />
{/if}
{/if}
</div>
{/if}
@@ -53,5 +53,6 @@
{canHavePreprocessor}
{hasPreprocessor}
{newItem}
alwaysOpened={true}
/>
</div>
@@ -7,7 +7,7 @@
import WebsocketTriggerEditor from './WebsocketTriggerEditor.svelte'
import { isCloudHosted } from '$lib/cloud'
import Section from '$lib/components/Section.svelte'
import { Alert, Skeleton } from '$lib/components/common'
import { Alert } from '$lib/components/common'
import Description from '$lib/components/Description.svelte'
import type { TriggerContext } from '$lib/components/triggers'
import TriggersEditorSection from '../TriggersEditorSection.svelte'
@@ -19,6 +19,8 @@
export let hasPreprocessor: boolean = false
let wsTriggerEditor: WebsocketTriggerEditor
let openForm = true
let dontCloseOnLoad = false
$: path && loadTriggers()
@@ -49,6 +51,7 @@
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
})
$triggersCount = { ...($triggersCount ?? {}), websocket_count: wsTriggers?.length }
openForm = wsTriggers?.length === 0 || dontCloseOnLoad
} catch (e) {
console.error('impossible to load WS triggers', e)
}
@@ -72,6 +75,36 @@
WebSocket triggers allow real-time bidirectional communication between your scripts/flows and
external systems. Each trigger creates a unique WebSocket endpoint.
</Description>
{#if !newItem && wsTriggers && wsTriggers.length > 0}
<Section label="WebSockets">
<div class="flex flex-col gap-4">
<div class="flex flex-col divide-y pt-2">
{#each wsTriggers as wsTriggers (wsTriggers.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{wsTriggers.path}</div>
<div class="col-span-2 truncate">
{wsTriggers.url}
</div>
<div class="flex justify-end">
<button
on:click={() => wsTriggerEditor?.openEdit(wsTriggers.path, isFlow)}
class="px-2"
>
{#if wsTriggers.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
</div>
</Section>
{/if}
<TriggersEditorSection
on:applyArgs
on:saveTrigger={(e) => {
@@ -88,43 +121,7 @@
{canHavePreprocessor}
{hasPreprocessor}
{newItem}
{openForm}
/>
{#if !newItem}
<Section label="WebSockets">
<div class="flex flex-col gap-4">
{#if wsTriggers}
{#if wsTriggers.length == 0}
<div class="text-xs text-secondary text-center"> No WebSocket triggers </div>
{:else}
<div class="flex flex-col divide-y pt-2">
{#each wsTriggers as wsTriggers (wsTriggers.path)}
<div class="grid grid-cols-5 text-2xs items-center py-2">
<div class="col-span-2 truncate">{wsTriggers.path}</div>
<div class="col-span-2 truncate">
{wsTriggers.url}
</div>
<div class="flex justify-end">
<button
on:click={() => wsTriggerEditor?.openEdit(wsTriggers.path, isFlow)}
class="px-2"
>
{#if wsTriggers.canWrite}
Edit
{:else}
View
{/if}
</button>
</div>
</div>
{/each}
</div>
{/if}
{:else}
<Skeleton layout={[[8]]} />
{/if}
</div>
</Section>
{/if}
</div>
{/if}
@@ -344,7 +344,7 @@
let stepDetail: FlowModule | string | undefined = undefined
let token = 'TOKEN_TO_CREATE'
let rightPaneSelected = 'saved_inputs'
let savedInputsV2: SavedInputsV2 | undefined = undefined
let flowHistory: FlowHistory | undefined = undefined
</script>
@@ -449,7 +449,12 @@
<div class="flex flex-col align-left">
<div class="flex flex-row justify-between">
<InputSelectedBadge {inputSelected} />
<InputSelectedBadge
on:click={() => {
savedInputsV2?.resetSelected()
}}
{inputSelected}
/>
<Toggle
bind:checked={jsonView}
label="JSON View"
@@ -525,6 +530,7 @@
</svelte:fragment>
<svelte:fragment slot="save_inputs">
<SavedInputsV2
bind:this={savedInputsV2}
{jsonView}
flowPath={flow?.path}
{isValid}
@@ -496,6 +496,8 @@
let token = 'TOKEN_TO_CREATE'
let rightPaneSelected = 'saved_inputs'
let savedInputsV2: SavedInputsV2 | undefined = undefined
</script>
<MoveDrawer
@@ -652,7 +654,12 @@
<div class="flex flex-col align-left">
<div class="flex flex-row justify-between">
<InputSelectedBadge {inputSelected} />
<InputSelectedBadge
on:click={() => {
savedInputsV2?.resetSelected()
}}
{inputSelected}
/>
<Toggle
bind:checked={jsonView}
label="JSON View"
@@ -715,6 +722,7 @@
<svelte:fragment slot="save_inputs">
{#if args}
<SavedInputsV2
bind:this={savedInputsV2}
scriptPath={script?.path}
scriptHash={topHash}
{isValid}
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.455.1"
wmill_pg = ">=1.455.1"
wmill = ">=1.456.0"
wmill_pg = ">=1.456.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.455.1
version: 1.456.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.455.1'
ModuleVersion = '1.456.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.455.1"
version = "1.456.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.455.1"
version = "1.456.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.455.1",
"version": "1.456.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.455.1",
"version": "1.456.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.455.1
1.456.0