diff --git a/CHANGELOG.md b/CHANGELOG.md index bf488aa816..08f7c83c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [1.418.0](https://github.com/windmill-labs/windmill/compare/v1.417.3...v1.418.0) (2024-11-04) + + +### Features + +* **frontend:** improve and simplify scheduled poll flows ([#4560](https://github.com/windmill-labs/windmill/issues/4560)) ([22ab51e](https://github.com/windmill-labs/windmill/commit/22ab51e9914376b47827f0be66708deef3a37767)) +* **oauth:** add snowflake oauth support ([#4622](https://github.com/windmill-labs/windmill/issues/4622)) ([693b7a4](https://github.com/windmill-labs/windmill/commit/693b7a4fd4da0e2b14cb6c8ea19537aa600225be)) + + +### Bug Fixes + +* **prometheus:** improve queue_count when tags have no more jobs ([09156b6](https://github.com/windmill-labs/windmill/commit/09156b65c2a0f0ac59fce1dede2b68b790323c49)) + +## [1.417.3](https://github.com/windmill-labs/windmill/compare/v1.417.2...v1.417.3) (2024-11-04) + + +### Bug Fixes + +* **cli:** improve pulling instance with folderPerInstance ([1e7909e](https://github.com/windmill-labs/windmill/commit/1e7909e95931e1e121cb3767bad093239254935d)) + ## [1.417.2](https://github.com/windmill-labs/windmill/compare/v1.417.1...v1.417.2) (2024-11-04) diff --git a/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json b/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json index 75b8108281..1fa370e682 100644 --- a/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json +++ b/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json @@ -5,7 +5,7 @@ "columns": [ { "ordinal": 0, - "name": "?column?", + "name": "bool", "type_info": "Bool" } ], diff --git a/backend/.sqlx/query-b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a.json b/backend/.sqlx/query-b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a.json new file mode 100644 index 0000000000..ba9f3814e8 --- /dev/null +++ b/backend/.sqlx/query-b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n hostname,\n mode::text,\n worker_group,\n log_ts,\n file_path,\n ok_lines,\n err_lines,\n json_fmt\n FROM log_file\n WHERE log_ts > $1\n ORDER BY log_ts ASC LIMIT $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hostname", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "mode", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "worker_group", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "log_ts", + "type_info": "Timestamp" + }, + { + "ordinal": 4, + "name": "file_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "ok_lines", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "err_lines", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "json_fmt", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Timestamp", + "Int8" + ] + }, + "nullable": [ + false, + null, + true, + false, + false, + true, + true, + true + ] + }, + "hash": "b5c839baab25c4dcdd503d380cf7a886242277cd50555f20b2e22e13942d2a3a" +} diff --git a/backend/.sqlx/query-dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6.json b/backend/.sqlx/query-dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6.json new file mode 100644 index 0000000000..b64db838d3 --- /dev/null +++ b/backend/.sqlx/query-dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n hostname,\n mode::text,\n worker_group,\n log_ts,\n file_path,\n ok_lines,\n err_lines,\n json_fmt\n FROM log_file\n ORDER BY log_ts ASC LIMIT $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "hostname", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "mode", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "worker_group", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "log_ts", + "type_info": "Timestamp" + }, + { + "ordinal": 4, + "name": "file_path", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "ok_lines", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "err_lines", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "json_fmt", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + null, + true, + false, + false, + true, + true, + true + ] + }, + "hash": "dd967c5983fa0ff05e2b320ad0e0b5a152784826cb8fb4381c1ffe228cb7feb6" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 4b9b38ea07..1c2f6b1fc2 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -3987,9 +3987,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" dependencies = [ "allocator-api2", "equivalent", @@ -4535,7 +4535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.1", "serde", ] @@ -4942,7 +4942,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.15.1", ] [[package]] @@ -9124,18 +9124,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", @@ -10563,7 +10563,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "axum", @@ -10605,7 +10605,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "argon2", @@ -10690,7 +10690,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.417.2" +version = "1.418.0" dependencies = [ "base64 0.22.1", "chrono", @@ -10708,7 +10708,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.417.2" +version = "1.418.0" dependencies = [ "chrono", "serde", @@ -10721,7 +10721,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "rsmq_async", @@ -10736,7 +10736,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "async-stream", @@ -10782,7 +10782,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.417.2" +version = "1.418.0" dependencies = [ "regex", "rsmq_async", @@ -10797,7 +10797,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "bytes", @@ -10819,7 +10819,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.417.2" +version = "1.418.0" dependencies = [ "itertools 0.13.0", "lazy_static", @@ -10831,7 +10831,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.417.2" +version = "1.418.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -10840,7 +10840,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "lazy_static", @@ -10852,7 +10852,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "gosyn", @@ -10864,7 +10864,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "lazy_static", @@ -10876,7 +10876,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10887,7 +10887,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10898,7 +10898,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "async-recursion", @@ -10916,7 +10916,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -10933,7 +10933,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "lazy_static", @@ -10945,7 +10945,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "lazy_static", @@ -10963,7 +10963,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -10984,7 +10984,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "serde_json", @@ -10994,7 +10994,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "async-recursion", @@ -11027,7 +11027,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.417.2" +version = "1.418.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -11037,7 +11037,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.417.2" +version = "1.418.0" dependencies = [ "anyhow", "async-recursion", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c61d8a69d7..b0d6e47120 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.417.2" +version = "1.418.0" authors.workspace = true edition.workspace = true @@ -29,7 +29,7 @@ members = [ ] [workspace.package] -version = "1.417.2" +version = "1.418.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index e7dd9365ec..2104f8ba8c 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -f136a2f499e0fe7c10c54c79488851980d796eb2 \ No newline at end of file +01f384348341c87c5a0b32c4be7c699975ee7d6d \ No newline at end of file diff --git a/backend/oauth_connect.json b/backend/oauth_connect.json index 994d9999fd..0608f0d3c4 100644 --- a/backend/oauth_connect.json +++ b/backend/oauth_connect.json @@ -171,5 +171,6 @@ "user-library-modify", "user-library-read" ] - } -} \ No newline at end of file + }, + "snowflake_oauth": {} +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 11d12f9dab..61ee657d2c 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -531,7 +531,7 @@ Windmill Community Edition {GIT_VERSION} #[cfg(feature = "tantivy")] let (index_reader, index_writer) = if should_index_jobs { - let (r, w) = windmill_indexer::indexer_ee::init_index(&db).await?; + let (r, w) = windmill_indexer::completed_runs_ee::init_index(&db).await?; (Some(r), Some(w)) } else { (None, None) @@ -543,26 +543,61 @@ Windmill Community Edition {GIT_VERSION} let index_writer2 = index_writer.clone(); async { if let Some(index_writer) = index_writer2 { - windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx) - .await; + windmill_indexer::completed_runs_ee::run_indexer( + db.clone(), + index_writer, + indexer_rx, + ) + .await; + } + Ok(()) + } + }; + + #[cfg(all(feature = "tantivy", feature = "parquet"))] + let (log_index_reader, log_index_writer) = if should_index_jobs { + let (r, w) = windmill_indexer::service_logs_ee::init_index(&db).await?; + (Some(r), Some(w)) + } else { + (None, None) + }; + + #[cfg(all(feature = "tantivy", feature = "parquet"))] + let log_indexer_f = { + let log_indexer_rx = killpill_rx.resubscribe(); + let log_index_writer2 = log_index_writer.clone(); + async { + if let Some(log_index_writer) = log_index_writer2 { + windmill_indexer::service_logs_ee::run_indexer( + db.clone(), + log_index_writer, + log_indexer_rx, + ) + .await; } Ok(()) } }; #[cfg(not(feature = "tantivy"))] - let (index_reader, index_writer) = (None, None); + let index_reader = None; #[cfg(not(feature = "tantivy"))] let indexer_f = async { Ok(()) as anyhow::Result<()> }; + #[cfg(not(all(feature = "tantivy", feature = "parquet")))] + let log_index_reader = None; + + #[cfg(not(all(feature = "tantivy", feature = "parquet")))] + let log_indexer_f = async { Ok(()) as anyhow::Result<()> }; + let server_f = async { if !is_agent { windmill_api::run_server( db.clone(), rsmq2, index_reader, - index_writer, + log_index_reader, addr, server_killpill_rx, base_internal_tx, @@ -842,7 +877,8 @@ Windmill Community Edition {GIT_VERSION} monitor_f, server_f, metrics_f, - indexer_f + indexer_f, + log_indexer_f )?; } else { tracing::info!("Nothing to do, exiting."); diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 0618cfd8d8..b6cbd55330 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -111,6 +111,9 @@ lazy_static::lazy_static! { "Number of jobs in the queue", &["tag"] ).unwrap(); + + static ref QUEUE_COUNT_TAGS: Arc>> = Arc::new(RwLock::new(Vec::new())); + } pub async fn initial_load( @@ -1089,12 +1092,23 @@ pub async fn expose_queue_metrics(db: &Pool) { if metrics_enabled || save_metrics { let queue_counts = windmill_common::queue::get_queue_counts(db).await; + if metrics_enabled { + for q in QUEUE_COUNT_TAGS.read().await.iter() { + if queue_counts.get(q).is_none() { + (*QUEUE_COUNT).with_label_values(&[q]).set(0); + } + } + } + + let mut tags_to_watch = vec![]; for q in queue_counts { let count = q.1; let tag = q.0; + if metrics_enabled { let metric = (*QUEUE_COUNT).with_label_values(&[&tag]); metric.set(count as i64); + tags_to_watch.push(tag.to_string()); } // save queue_count and delay metrics per tag @@ -1119,6 +1133,10 @@ pub async fn expose_queue_metrics(db: &Pool) { } } } + if metrics_enabled { + let mut w = QUEUE_COUNT_TAGS.write().await; + *w = tags_to_watch; + } } // clean queue metrics older than 14 days diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index a93c6eef1d..69482f3922 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -1112,6 +1112,7 @@ async fn test_deno_flow(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, } .into(), stop_after_if: Default::default(), @@ -1153,6 +1154,7 @@ async fn test_deno_flow(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, } .into(), stop_after_if: Default::default(), @@ -1276,6 +1278,8 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, + }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), @@ -1327,6 +1331,7 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), @@ -1364,6 +1369,8 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, + }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), @@ -1424,6 +1431,7 @@ async fn test_deno_flow_same_worker(db: Pool) { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, }.into(), stop_after_if: Default::default(), stop_after_all_iters_if: Default::default(), diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 70e9784210..0949843665 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.417.2 + version: 1.418.0 title: Windmill API contact: @@ -9546,6 +9546,109 @@ paths: items: $ref: "#/components/schemas/JobSearchHit" + /srch/index/search/service_logs: + get: + summary: Search through service logs with a string query + operationId: searchLogsIndex + tags: + - indexSearch + parameters: + - name: search_query + in: query + required: true + schema: + type: string + - name: mode + in: query + required: true + schema: + type: string + - name: worker_group + in: query + required: false + schema: + type: string + - name: hostname + in: query + required: true + schema: + type: string + - name: min_ts + in: query + required: false + schema: + type: string + format: date-time + - name: max_ts + in: query + required: false + schema: + type: string + format: date-time + responses: + "200": + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: a list of the terms that couldn't be parsed (and thus ignored) + type: array + items: + type: string + hits: + description: log files that matched the query + type: array + items: + $ref: "#/components/schemas/LogSearchHit" + + /srch/index/search/count_service_logs: + get: + summary: Search and count the log line hits on every provided host + operationId: countSearchLogsIndex + tags: + - indexSearch + parameters: + - name: search_query + in: query + required: true + schema: + type: string + - name: hosts + in: query + required: true + schema: + type: string + - name: min_ts + in: query + required: false + schema: + type: string + format: date-time + - name: max_ts + in: query + required: false + schema: + type: string + format: date-time + responses: + "200": + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: a list of the terms that couldn't be parsed (and thus ignored) + type: array + items: + type: string + count_per_host: + description: count of log lines that matched the query per hostname + type: object components: securitySchemes: @@ -12500,6 +12603,12 @@ components: dancer: type: string + LogSearchHit: + type: object + properties: + dancer: + type: string + AutoscalingEvent: type: object properties: @@ -12517,3 +12626,4 @@ components: applied_at: type: string format: date-time + diff --git a/backend/windmill-api/src/flows.rs b/backend/windmill-api/src/flows.rs index 4fc9ce514f..f02f91dc41 100644 --- a/backend/windmill-api/src/flows.rs +++ b/backend/windmill-api/src/flows.rs @@ -1209,6 +1209,7 @@ mod tests { .into(), hash: None, tag_override: None, + is_trigger: None, }), stop_after_if: None, stop_after_all_iters_if: None, @@ -1236,6 +1237,7 @@ mod tests { custom_concurrency_key: None, concurrent_limit: None, concurrency_time_window_s: None, + is_trigger: None, }), stop_after_if: Some(StopAfterIf { expr: "foo = 'bar'".to_string(), @@ -1290,6 +1292,7 @@ mod tests { input_transforms: HashMap::new(), hash: None, tag_override: None, + is_trigger: None, } .into(), stop_after_if: Some(StopAfterIf { diff --git a/backend/windmill-api/src/indexer_ee.rs b/backend/windmill-api/src/indexer_ee.rs index 61a946bb84..2ccca92c27 100644 --- a/backend/windmill-api/src/indexer_ee.rs +++ b/backend/windmill-api/src/indexer_ee.rs @@ -3,3 +3,7 @@ use axum::Router; pub fn workspaced_service() -> Router { Router::new() } + +pub fn global_service() -> Router { + Router::new() +} diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 3ea78ffd6d..5a7f7fcded 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -154,18 +154,18 @@ pub async fn add_webhook_allowed_origin( type IndexReader = (); #[cfg(not(feature = "tantivy"))] -type IndexWriter = (); +type ServiceLogIndexReader = (); #[cfg(feature = "tantivy")] -type IndexReader = windmill_indexer::indexer_ee::IndexReader; +type IndexReader = windmill_indexer::completed_runs_ee::IndexReader; #[cfg(feature = "tantivy")] -type IndexWriter = windmill_indexer::indexer_ee::IndexWriter; +type ServiceLogIndexReader = windmill_indexer::service_logs_ee::ServiceLogIndexReader; pub async fn run_server( db: DB, rsmq: Option, - index_reader: Option, - index_writer: Option, + job_index_reader: Option, + log_index_reader: Option, addr: SocketAddr, mut rx: tokio::sync::broadcast::Receiver<()>, port_tx: tokio::sync::oneshot::Sender, @@ -205,8 +205,9 @@ pub async fn run_server( .layer(Extension(rsmq.clone())) .layer(Extension(user_db.clone())) .layer(Extension(auth_cache.clone())) - .layer(Extension(index_reader)) - .layer(Extension(index_writer)) + .layer(Extension(job_index_reader)) + .layer(Extension(log_index_reader)) + // .layer(Extension(index_writer)) .layer(CookieManagerLayer::new()) .layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone()))) .layer(DefaultBodyLimit::max( @@ -322,6 +323,10 @@ pub async fn run_server( "/srch/w/:workspace_id/index", indexer_ee::workspaced_service(), ) + .nest( + "/srch/index", + indexer_ee::global_service(), + ) .nest("/oidc", oidc_ee::global_service()) .nest( "/saml", diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 9b66cc65ac..d357a5ec72 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -426,6 +426,8 @@ pub enum FlowModuleValue { hash: Option, #[serde(skip_serializing_if = "Option::is_none")] tag_override: Option, + #[serde(skip_serializing_if = "Option::is_none")] + is_trigger: Option, }, Flow { #[serde(default)] @@ -474,6 +476,8 @@ pub enum FlowModuleValue { concurrent_limit: Option, #[serde(skip_serializing_if = "Option::is_none")] concurrency_time_window_s: Option, + #[serde(skip_serializing_if = "Option::is_none")] + is_trigger: Option, }, Identity, } @@ -505,6 +509,7 @@ struct UntaggedFlowModuleValue { custom_concurrency_key: Option, concurrent_limit: Option, concurrency_time_window_s: Option, + is_trigger: Option, } impl<'de> Deserialize<'de> for FlowModuleValue { @@ -522,6 +527,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue { .ok_or_else(|| serde::de::Error::missing_field("path"))?, hash: untagged.hash, tag_override: untagged.tag_override, + is_trigger: untagged.is_trigger, }), "flow" => Ok(FlowModuleValue::Flow { input_transforms: untagged.input_transforms.unwrap_or_default(), @@ -574,6 +580,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue { custom_concurrency_key: untagged.custom_concurrency_key, concurrent_limit: untagged.concurrent_limit, concurrency_time_window_s: untagged.concurrency_time_window_s, + is_trigger: untagged.is_trigger, }), "identity" => Ok(FlowModuleValue::Identity), other => Err(serde::de::Error::unknown_variant( diff --git a/backend/windmill-indexer/src/completed_runs_ee.rs b/backend/windmill-indexer/src/completed_runs_ee.rs new file mode 100644 index 0000000000..79bcbcff77 --- /dev/null +++ b/backend/windmill-indexer/src/completed_runs_ee.rs @@ -0,0 +1,21 @@ +use sqlx::{Pool, Postgres}; +use windmill_common::error::Error; +use anyhow::anyhow; + +#[derive(Clone)] +pub struct IndexReader; + +#[derive(Clone)] +pub struct IndexWriter; + +pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { + Err(anyhow!("Cannot initialize index: not in EE").into()) +} + +pub async fn run_indexer( + _db: Pool, + mut _index_writer: IndexWriter, + mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, +) { + tracing::error!("Cannot run indexer: not in EE"); +} diff --git a/backend/windmill-indexer/src/indexer_ee.rs b/backend/windmill-indexer/src/indexer_ee.rs index da92ff0ef8..e69de29bb2 100644 --- a/backend/windmill-indexer/src/indexer_ee.rs +++ b/backend/windmill-indexer/src/indexer_ee.rs @@ -1,21 +0,0 @@ -use anyhow::anyhow; -use sqlx::{Pool, Postgres}; -use windmill_common::error::Error; - -#[derive(Clone)] -pub struct IndexReader; - -#[derive(Clone)] -pub struct IndexWriter; - -pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { - Err(anyhow!("Cannot initialize index: not in EE").into()) -} - -pub async fn run_indexer( - _db: Pool, - mut _index_writer: IndexWriter, - mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, -) { - tracing::error!("Cannot run indexer: not in EE"); -} diff --git a/backend/windmill-indexer/src/lib.rs b/backend/windmill-indexer/src/lib.rs index c1bf4eea0c..14c6e7906d 100644 --- a/backend/windmill-indexer/src/lib.rs +++ b/backend/windmill-indexer/src/lib.rs @@ -1 +1,3 @@ +pub mod completed_runs_ee; +pub mod service_logs_ee; pub mod indexer_ee; diff --git a/backend/windmill-indexer/src/service_logs_ee.rs b/backend/windmill-indexer/src/service_logs_ee.rs new file mode 100644 index 0000000000..79bcbcff77 --- /dev/null +++ b/backend/windmill-indexer/src/service_logs_ee.rs @@ -0,0 +1,21 @@ +use sqlx::{Pool, Postgres}; +use windmill_common::error::Error; +use anyhow::anyhow; + +#[derive(Clone)] +pub struct IndexReader; + +#[derive(Clone)] +pub struct IndexWriter; + +pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { + Err(anyhow!("Cannot initialize index: not in EE").into()) +} + +pub async fn run_indexer( + _db: Pool, + mut _index_writer: IndexWriter, + mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, +) { + tracing::error!("Cannot run indexer: not in EE"); +} diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index c0235c9b26..9b94c0f198 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -3522,6 +3522,7 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>( path: path.clone(), hash: Some(hash), tag_override: tag_override, + is_trigger: None, }, ), stop_after_if: None, diff --git a/backend/windmill-worker/src/snowflake_executor.rs b/backend/windmill-worker/src/snowflake_executor.rs index f89b29832a..9f8d3e12fd 100644 --- a/backend/windmill-worker/src/snowflake_executor.rs +++ b/backend/windmill-worker/src/snowflake_executor.rs @@ -32,9 +32,9 @@ struct Claims { #[derive(Deserialize)] struct SnowflakeDatabase { account_identifier: String, - public_key: String, - private_key: String, - username: String, + public_key: Option, + private_key: Option, + username: Option, database: Option, schema: Option, warehouse: Option, @@ -119,6 +119,7 @@ fn do_snowflake_inner<'a>( mut body: serde_json::Map, account_identifier: &'a str, token: &'a str, + token_is_keypair: bool, column_order: Option<&'a mut Option>>, skip_collect: bool, ) -> windmill_common::error::Result>>> { @@ -144,16 +145,19 @@ fn do_snowflake_inner<'a>( } let result_f = async move { - let result = HTTP_CLIENT + let mut request = HTTP_CLIENT .post(format!( "https://{}.snowflakecomputing.com/api/v2/statements/", account_identifier.to_uppercase() )) .bearer_auth(token) - .header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT") - .json(&body) - .send() - .await; + .json(&body); + + if token_is_keypair { + request = request.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT"); + } + + let result = request.send().await; if skip_collect { handle_snowflake_result(result).await?; @@ -189,11 +193,17 @@ fn do_snowflake_inner<'a>( account_identifier.to_uppercase(), response.statementHandle ); - let response = HTTP_CLIENT + let mut request = HTTP_CLIENT .get(url) .bearer_auth(token) - .header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT") - .query(&[("partition", idx.to_string())]) + .query(&[("partition", idx.to_string())]); + + if token_is_keypair { + request = + request.header("X-Snowflake-Authorization-Token-Type", "KEYPAIR_JWT"); + } + + let response = request .send() .await .parse_snowflake_response::() @@ -258,7 +268,7 @@ pub async fn do_snowflake( snowflake_args.get("database").cloned() }; - let database = if let Some(db) = db_arg { + let database = if let Some(ref db) = db_arg { serde_json::from_value::(db.clone()) .map_err(|e| Error::ExecutionErr(e.to_string()))? } else { @@ -267,37 +277,59 @@ pub async fn do_snowflake( let annotations = windmill_common::worker::SqlAnnotations::parse(query); - let qualified_username = format!( - "{}.{}", - database.account_identifier.split('.').next().unwrap_or(""), // get first part of account identifier - database.username - ) - .to_uppercase(); + // Check if the token is present in db_arg and use it if available + let (token, token_is_keypair) = if let Some(token) = db_arg + .as_ref() + .and_then(|db| db.get("token")) + .and_then(|t| t.as_str()) + .filter(|t| !t.is_empty()) + { + tracing::debug!("Using oauth token from db_arg"); + (token.to_string(), false) + } else { + tracing::debug!("Generating new oauth token"); - let public_key = pem::parse(database.public_key.as_bytes()).map_err(|e| { - Error::ExecutionErr(format!("Failed to parse public key: {}", e.to_string())) - })?; - let mut public_key_hash = Sha256::new(); - public_key_hash.update(public_key.contents()); + let qualified_username = format!( + "{}.{}", + database.account_identifier.split('.').next().unwrap_or(""), + database.username.as_deref().unwrap_or("") + ) + .to_uppercase(); - let public_key_fp = engine::general_purpose::STANDARD.encode(public_key_hash.finalize()); + let public_key = match database.public_key.as_deref() { + Some(key) => pem::parse(key.as_bytes()).map_err(|e| { + Error::ExecutionErr(format!("Failed to parse public key: {}", e.to_string())) + })?, + None => return Err(Error::ExecutionErr("Public key is missing".to_string())), + }; + let mut public_key_hash = Sha256::new(); + public_key_hash.update(public_key.contents()); - let iss = format!("{}.SHA256:{}", qualified_username, public_key_fp); + let public_key_fp = engine::general_purpose::STANDARD.encode(public_key_hash.finalize()); - let claims = Claims { - iss: iss, - sub: qualified_username, - iat: chrono::Utc::now().timestamp(), - exp: (chrono::Utc::now() + chrono::Duration::try_hours(1).unwrap()).timestamp(), + let iss = format!("{}.SHA256:{}", qualified_username, public_key_fp); + + let claims = Claims { + iss: iss, + sub: qualified_username, + iat: chrono::Utc::now().timestamp(), + exp: (chrono::Utc::now() + chrono::Duration::try_hours(1).unwrap()).timestamp(), + }; + + let private_key = match database.private_key.as_deref() { + Some(key) => EncodingKey::from_rsa_pem(key.as_bytes()).map_err(|e| { + Error::ExecutionErr(format!("Failed to parse private key: {}", e.to_string())) + })?, + None => return Err(Error::ExecutionErr("Private key is missing".to_string())), + }; + + ( + encode(&Header::new(Algorithm::RS256), &claims, &private_key) + .map_err(|e| Error::ExecutionErr(e.to_string()))?, + true, + ) }; - let private_key = EncodingKey::from_rsa_pem(database.private_key.as_bytes()).map_err(|e| { - Error::ExecutionErr(format!("Failed to parse private key: {}", e.to_string())) - })?; - - let token = encode(&Header::new(Algorithm::RS256), &claims, &private_key) - .map_err(|e| Error::ExecutionErr(e.to_string()))?; - tracing::debug!("Snowflake token: {}", token); let mut body = serde_json::Map::new(); @@ -344,6 +376,7 @@ pub async fn do_snowflake( body.clone(), &database.account_identifier, &token, + token_is_keypair, None, annotations.return_last_result && i < queries.len() - 1, ) @@ -371,6 +404,7 @@ pub async fn do_snowflake( body.clone(), &database.account_identifier, &token, + token_is_keypair, Some(column_order), false, )? diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index f71c73f618..6c88294126 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -735,6 +735,7 @@ async fn lock_modules<'c>( custom_concurrency_key, concurrent_limit, concurrency_time_window_s, + is_trigger, } = e.get_value()? else { match e.get_value()? { @@ -970,6 +971,7 @@ async fn lock_modules<'c>( custom_concurrency_key, concurrent_limit, concurrency_time_window_s, + is_trigger, }); new_flow_modules.push(e); continue; @@ -992,6 +994,7 @@ async fn lock_modules<'c>( custom_concurrency_key, concurrent_limit, concurrency_time_window_s, + is_trigger, }); new_flow_modules.push(e); continue; diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index cb981a3e41..961d105aa1 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -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.417.2"; +export const VERSION = "v1.418.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/instance.ts b/cli/instance.ts index d271f5627e..e80db19700 100644 --- a/cli/instance.ts +++ b/cli/instance.ts @@ -575,13 +575,14 @@ async function getLocalWorkspaces( } if (folderPerInstance) { for await (const dir of Deno.readDir(rootDir + "/" + localPrefix)) { - const dirName = dir.name; - localWorkspaces.push({ - dir: localPrefix + "/" + dirName, - id: dirName, - }); + if (dir.isDirectory) { + const dirName = dir.name; + localWorkspaces.push({ + dir: localPrefix + "/" + dirName, + id: dirName, + }); + } } - log.info(localWorkspaces); } else { for await (const dir of Deno.readDir(rootDir)) { const dirName = dir.name; @@ -593,6 +594,9 @@ async function getLocalWorkspaces( } } } + log.info( + "Local workspaces found: " + localWorkspaces.map((x) => x.id).join(", ") + ); return localWorkspaces; } diff --git a/cli/main.ts b/cli/main.ts index 7005442b98..92039f51f8 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.417.2"; +export const VERSION = "1.418.0"; const command = new Command() .name("wmill") diff --git a/cli/workspace.ts b/cli/workspace.ts index 4338e21cfe..cd0ff31ee9 100644 --- a/cli/workspace.ts +++ b/cli/workspace.ts @@ -285,8 +285,8 @@ export async function removeWorkspace( } return; } - if (silent) { - log.info(colors.yellow(`Replacing existing workspace ${name}`)); + if (!silent) { + log.info(colors.yellow(`Removing existing workspace ${name}`)); } await Deno.writeTextFile( diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c77d286871..a5c5e883d1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.417.2", + "version": "1.418.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.417.2", + "version": "1.418.0", "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", @@ -43,7 +43,7 @@ "graphql": "^16.7.1", "hash-sum": "^2.0.0", "highlight.js": "^11.8.0", - "lucide-svelte": "^0.293.0", + "lucide-svelte": "^0.399.0", "minimatch": "^10.0.1", "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~8.0.2", "monaco-editor-wrapper": "^5.5.2", @@ -8607,11 +8607,11 @@ "optional": true }, "node_modules/lucide-svelte": { - "version": "0.293.0", - "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.293.0.tgz", - "integrity": "sha512-nQ6QO6anjjiVoipw3dNptzI6NWuiArlhysVtRpoAWICSQVMR3ybwVf6piUUqbjGe+RfGufLzCl93C/yhsS+p4w==", + "version": "0.399.0", + "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.399.0.tgz", + "integrity": "sha512-NQ8AxNMKbIJsx7HV//gnAsIY1wJfb3rbXSK2S/ZDjIldvAEdzGngpUT8T8Q8zHYUuii0bavAmVARN8giR4vvpA==", "peerDependencies": { - "svelte": ">=3 <5" + "svelte": "^3 || ^4 || ^5.0.0-next.42" } }, "node_modules/magic-string": { diff --git a/frontend/package.json b/frontend/package.json index 9ce269d708..1716d5546a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.417.2", + "version": "1.418.0", "scripts": { "dev": "vite dev", "build": "vite build", @@ -116,7 +116,7 @@ "graphql": "^16.7.1", "hash-sum": "^2.0.0", "highlight.js": "^11.8.0", - "lucide-svelte": "^0.293.0", + "lucide-svelte": "^0.399.0", "minimatch": "^10.0.1", "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~8.0.2", "monaco-editor-wrapper": "^5.5.2", @@ -182,6 +182,11 @@ "svelte": "./package/components/icons/WindmillIcon2.svelte", "default": "./package/components/icons/WindmillIcon2.svelte" }, + "./components/icons/SchedulePollIcon.svelte": { + "types": "./package/components/icons/SchedulePollIcon.d.ts", + "svelte": "./package/components/icons/SchedulePollIcon.svelte", + "default": "./package/components/icons/SchedulePollIcon.svelte" + }, "./components/IconedResourceType.svelte": { "types": "./package/components/IconedResourceType.svelte.d.ts", "svelte": "./package/components/IconedResourceType.svelte", diff --git a/frontend/src/lib/components/AppConnectInner.svelte b/frontend/src/lib/components/AppConnectInner.svelte index e568589b49..8ea77cc12d 100644 --- a/frontend/src/lib/components/AppConnectInner.svelte +++ b/frontend/src/lib/components/AppConnectInner.svelte @@ -250,6 +250,14 @@ throw Error(`Resource at path ${path} already exists. Delete it or pick another path`) } + + if (resourceType == 'snowflake_oauth') { + const account_identifier = extra_params.find(([key, _]) => key == 'account_identifier') + if (account_identifier) { + args['account_identifier'] = account_identifier[1] + } + } + let account: number | undefined = undefined if (valueToken?.expires_in != undefined) { account = Number( diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index 5e3b52d867..bde2bd265f 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -476,7 +476,7 @@ const testStepStore = writable>({}) const selectedIdStore = writable('settings-metadata') const selectedTriggerStore = writable< - 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll' >('webhooks') const primaryScheduleStore = writable(undefined) @@ -484,7 +484,8 @@ setContext('TriggerContext', { primarySchedule: primaryScheduleStore, selectedTrigger: selectedTriggerStore, - triggersCount: triggersCount + triggersCount: triggersCount, + simplifiedPoll: writable(false) }) setContext('FlowEditorContext', { selectedId: selectedIdStore, diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index b04fbdf8dc..f51b7e8bc8 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -144,7 +144,7 @@ ? { schedule_count: 1, primary_schedule: { schedule: savedPrimarySchedule.cron } } : undefined ) - + const simplifiedPoll = writable(false) export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) { primaryScheduleStore.set(schedule) loadTriggers() @@ -468,7 +468,7 @@ const selectedIdStore = writable(selectedId ?? 'settings-metadata') const selectedTriggerStore = writable< - 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll' >('webhooks') export function getSelectedId() { @@ -490,7 +490,14 @@ } function selectTrigger( - selectedTrigger: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' + selectedTrigger: + | 'webhooks' + | 'emails' + | 'schedules' + | 'cli' + | 'routes' + | 'websockets' + | 'scheduledPoll' ) { selectedTriggerStore.set(selectedTrigger) } @@ -517,7 +524,8 @@ setContext('TriggerContext', { selectedTrigger: selectedTriggerStore, primarySchedule: primaryScheduleStore, - triggersCount + triggersCount, + simplifiedPoll }) async function loadTriggers() { @@ -876,7 +884,6 @@ await tick() select(module.id) await tick() - await tick() focusCopilot() let isFirstInLoop = false diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index e881dad539..ec6982a5df 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -1008,6 +1008,7 @@ modules={job.raw_flow?.modules ?? []} failureModule={job.raw_flow?.failure_module} preprocessorModule={job.raw_flow?.preprocessor_module} + allowSimplifiedPoll={false} />
Object.entries(x)) @@ -217,7 +232,8 @@ 'linkedin', 'quickbooks', 'visma', - 'spotify' + 'spotify', + 'snowflake_oauth' ] let oauth_name = undefined @@ -269,6 +285,23 @@ } return true } + + let snowflakeAccountIdentifier = '' + + function setupSnowflakeUrls() { + // strip all whitespaces from account identifier + snowflakeAccountIdentifier = snowflakeAccountIdentifier.replace(/\s/g, '') + + const connect_config = { + scopes: [], + auth_url: `https://${snowflakeAccountIdentifier}.snowflakecomputing.com/oauth/authorize`, + token_url: `https://${snowflakeAccountIdentifier}.snowflakecomputing.com/oauth/token-request`, + req_body_auth: false, + extra_params: { account_identifier: snowflakeAccountIdentifier }, + extra_params_callback: {} + } + oauths['snowflake_oauth'].connect_config = connect_config + }
@@ -513,6 +546,22 @@ {#if !windmillBuiltins.includes(k) && k != 'slack'} {/if} + {#if k == 'snowflake_oauth'} + + {/if}
{/if} diff --git a/frontend/src/lib/components/LogSnippetViewer.svelte b/frontend/src/lib/components/LogSnippetViewer.svelte new file mode 100644 index 0000000000..693522ed33 --- /dev/null +++ b/frontend/src/lib/components/LogSnippetViewer.svelte @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/lib/components/LogViewer.svelte b/frontend/src/lib/components/LogViewer.svelte index de78eefc52..d04b7f9c56 100644 --- a/frontend/src/lib/components/LogViewer.svelte +++ b/frontend/src/lib/components/LogViewer.svelte @@ -245,7 +245,7 @@ : 'top-2'} left-36">mem peak: {(mem / 1024).toPrecision(4)}MB {/if} -
{#if content}{@const len =
 					(content?.length ?? 0) +
 					(loadedFromObjectStore?.length ?? 0)}{#if downloadStartUrl}
-			
-			{#if initialPrimarySchedule != undefined && initialPrimarySchedule != false && !newItem}
+			
+ +
+ {#if $initialPrimarySchedule != undefined && $initialPrimarySchedule != false && !newItem} @@ -274,12 +208,12 @@ {/if}