mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 08:01:26 +00:00
refactor: extract 12 leaf crates from windmill-api (#7899)
* feat(backend): extract 12 leaf crates from windmill-api to improve incremental compilation Extract independent modules from windmill-api (90k LOC monolith) into separate leaf crates to reduce incremental compilation times. Modules extracted: assets, configs, debug, flow-conversations, inputs, npm-proxy, openapi, schedule, settings, workers, agent-workers, and alerting (from windmill-common). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add windmill-api-settings dep to root crate, make ee_oss public Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add agent_workers integration tests 8 tests covering agent worker lifecycle: - Simple script execution (bun) - Script with arguments - Script with logs (verified in job_logs table) - Script failure handling - Complex result (nested objects/arrays) - Agent token creation via API - Token creation + Initial/MainLoop ping cycle - Multiple sequential job execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,3 +19,4 @@ backend/target
|
||||
frontend/node_modules
|
||||
typescript-client/node_modules
|
||||
frontend/.svelte-kit
|
||||
backend/chrome_profiler.json
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts \n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2 THEN\n CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n ELSE true\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "80864158f61adaad8df934acc54ba523c9f17d106298d8781885134d28553d36"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE alerts\n SET\n acknowledged = true,\n acknowledged_workspace = CASE\n WHEN $2 THEN\n CASE\n WHEN $1::text IS NOT NULL THEN true\n ELSE acknowledged_workspace\n END\n ELSE true\n END\n WHERE ($1::text IS NOT NULL AND workspace_id = $1)\n OR ($1::text IS NULL)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e00f7877d7bd33cf81e7bf48b1a37a507ab0a8f8b4bd7a38cf44d181f8f2d940"
|
||||
}
|
||||
+5
-1
@@ -37,11 +37,15 @@ Windmill uses a workspace-based architecture with multiple crates:
|
||||
- Update database schema with migration if necessary
|
||||
- Use `sqlx` for database operations with prepared statements
|
||||
- Use transactions for multi-step operations
|
||||
- To apply pending migrations: `sqlx migrate run` (never manually run .sql files)
|
||||
- **Never use `SQLX_OFFLINE=true`** — a live database is always available for compilation
|
||||
- After all code changes are done, run `./update-sqlx` to regenerate the offline query cache
|
||||
|
||||
## Enterprise Features
|
||||
|
||||
- Enterprise files use the `*_ee.rs` suffix
|
||||
- Enterprise source is in `windmill-ee-private` folder (sibling directory), symlinked into each crate's `src/`
|
||||
- Enterprise source is in `windmill-ee-private` folder (sibling directory at `../../windmill-ee-private`), symlinked into each crate's `src/`
|
||||
- You can and should modify `windmill-ee-private` directly when needed (e.g., when creating new crates that need EE code, mirror the package structure there)
|
||||
- Use feature flags: `#[cfg(feature = "enterprise")]`
|
||||
- Isolate enterprise code in separate modules
|
||||
|
||||
|
||||
Generated
+230
@@ -15768,7 +15768,9 @@ dependencies = [
|
||||
"url",
|
||||
"uuid",
|
||||
"windmill-api",
|
||||
"windmill-api-agent-workers",
|
||||
"windmill-api-client",
|
||||
"windmill-api-settings",
|
||||
"windmill-autoscaling",
|
||||
"windmill-common",
|
||||
"windmill-dep-map",
|
||||
@@ -15781,6 +15783,19 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tracing",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.630.2"
|
||||
@@ -15867,13 +15882,25 @@ dependencies = [
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"windmill-alerting",
|
||||
"windmill-api-agent-workers",
|
||||
"windmill-api-assets",
|
||||
"windmill-api-auth",
|
||||
"windmill-api-configs",
|
||||
"windmill-api-debug",
|
||||
"windmill-api-embeddings",
|
||||
"windmill-api-flow-conversations",
|
||||
"windmill-api-groups",
|
||||
"windmill-api-inputs",
|
||||
"windmill-api-jobs",
|
||||
"windmill-api-npm-proxy",
|
||||
"windmill-api-openapi",
|
||||
"windmill-api-schedule",
|
||||
"windmill-api-scripts",
|
||||
"windmill-api-settings",
|
||||
"windmill-api-sse",
|
||||
"windmill-api-users",
|
||||
"windmill-api-workers",
|
||||
"windmill-api-workspaces",
|
||||
"windmill-audit",
|
||||
"windmill-autoscaling",
|
||||
@@ -15905,6 +15932,42 @@ dependencies = [
|
||||
"windmill-worker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"http 1.4.0",
|
||||
"hyper 1.8.1",
|
||||
"lazy_static",
|
||||
"quick_cache",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
"windmill-parser-py-imports",
|
||||
"windmill-queue",
|
||||
"windmill-worker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.630.2"
|
||||
@@ -15941,6 +16004,46 @@ dependencies = [
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"windmill-api-auth",
|
||||
"windmill-audit",
|
||||
"windmill-autoscaling",
|
||||
"windmill-common",
|
||||
"windmill-worker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"ed25519-dalek",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"rand 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-api-auth",
|
||||
"windmill-audit",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.630.2"
|
||||
@@ -15964,6 +16067,22 @@ dependencies = [
|
||||
"windmill-store",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"serde",
|
||||
"sql-builder",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.630.2"
|
||||
@@ -15984,6 +16103,20 @@ dependencies = [
|
||||
"windmill-git-sync",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sql-builder",
|
||||
"sqlx",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.630.2"
|
||||
@@ -16009,6 +16142,63 @@ dependencies = [
|
||||
"windmill-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"flate2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tar",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"http 1.4.0",
|
||||
"indexmap 2.11.1",
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yml",
|
||||
"sqlx",
|
||||
"url",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
"windmill-store",
|
||||
"windmill-trigger-http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sql-builder",
|
||||
"sqlx",
|
||||
"tracing",
|
||||
"windmill-api-auth",
|
||||
"windmill-api-settings",
|
||||
"windmill-audit",
|
||||
"windmill-common",
|
||||
"windmill-git-sync",
|
||||
"windmill-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.630.2"
|
||||
@@ -16038,6 +16228,32 @@ dependencies = [
|
||||
"windmill-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"object_store",
|
||||
"regex",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-alerting",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.630.2"
|
||||
@@ -16073,6 +16289,20 @@ dependencies = [
|
||||
"windmill-git-sync",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.630.2"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"uuid",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.630.2"
|
||||
|
||||
+30
-4
@@ -26,7 +26,19 @@ members = [
|
||||
"./windmill-trigger-gcp",
|
||||
"./windmill-trigger-http",
|
||||
"./windmill-native-triggers",
|
||||
"./windmill-alerting",
|
||||
"./windmill-api-agent-workers",
|
||||
"./windmill-api-assets",
|
||||
"./windmill-api-configs",
|
||||
"./windmill-api-debug",
|
||||
"./windmill-api-embeddings",
|
||||
"./windmill-api-flow-conversations",
|
||||
"./windmill-api-inputs",
|
||||
"./windmill-api-npm-proxy",
|
||||
"./windmill-api-openapi",
|
||||
"./windmill-api-schedule",
|
||||
"./windmill-api-settings",
|
||||
"./windmill-api-workers",
|
||||
"./windmill-store",
|
||||
"./windmill-queue",
|
||||
"./windmill-worker",
|
||||
@@ -80,9 +92,9 @@ lto = "thin"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-api/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
private = ["windmill-api/private", "windmill-api-agent-workers?/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server", "dep:windmill-api-agent-workers"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-api-agent-workers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"]
|
||||
local_reports = ["windmill-common/local_reports"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
|
||||
stripe = ["windmill-api/stripe"]
|
||||
@@ -114,7 +126,7 @@ native_trigger = ["windmill-api/native_trigger"]
|
||||
sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"]
|
||||
gcp_trigger = ["windmill-api/gcp_trigger"]
|
||||
smtp = ["windmill-api/smtp", "windmill-common/smtp", "windmill-queue/smtp"]
|
||||
license = ["windmill-api/license"]
|
||||
license = ["windmill-api/license", "windmill-api-settings/license"]
|
||||
oauth2 = ["windmill-api/oauth2"]
|
||||
zip = ["windmill-api/zip"]
|
||||
static_frontend = ["windmill-api/static_frontend"]
|
||||
@@ -182,6 +194,8 @@ windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-api = { workspace = true, default-features = false }
|
||||
windmill-api-agent-workers = { workspace = true, optional = true }
|
||||
windmill-api-settings.workspace = true
|
||||
windmill-worker.workspace = true
|
||||
windmill-indexer = { workspace = true, optional = true }
|
||||
windmill-autoscaling = { workspace = true, optional = true }
|
||||
@@ -269,7 +283,19 @@ windmill-trigger-sqs = { path = "./windmill-trigger-sqs" }
|
||||
windmill-trigger-gcp = { path = "./windmill-trigger-gcp" }
|
||||
windmill-trigger-http = { path = "./windmill-trigger-http" }
|
||||
windmill-native-triggers = { path = "./windmill-native-triggers" }
|
||||
windmill-alerting = { path = "./windmill-alerting" }
|
||||
windmill-api-agent-workers = { path = "./windmill-api-agent-workers" }
|
||||
windmill-api-assets = { path = "./windmill-api-assets" }
|
||||
windmill-api-configs = { path = "./windmill-api-configs" }
|
||||
windmill-api-debug = { path = "./windmill-api-debug" }
|
||||
windmill-api-embeddings = { path = "./windmill-api-embeddings" }
|
||||
windmill-api-flow-conversations = { path = "./windmill-api-flow-conversations" }
|
||||
windmill-api-inputs = { path = "./windmill-api-inputs" }
|
||||
windmill-api-npm-proxy = { path = "./windmill-api-npm-proxy" }
|
||||
windmill-api-openapi = { path = "./windmill-api-openapi" }
|
||||
windmill-api-schedule = { path = "./windmill-api-schedule" }
|
||||
windmill-api-settings = { path = "./windmill-api-settings" }
|
||||
windmill-api-workers = { path = "./windmill-api-workers" }
|
||||
windmill-store = { path = "./windmill-store" }
|
||||
windmill-parser = { path = "./parsers/windmill-parser" }
|
||||
windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
|
||||
|
||||
@@ -1 +1 @@
|
||||
0eaf364f5421db5710318eb36c062114ab6ca081
|
||||
b2bd490cd14e7794bebbd8737c2833ea671370b3
|
||||
@@ -0,0 +1,269 @@
|
||||
#![cfg(all(feature = "private", feature = "agent_worker_server"))]
|
||||
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::{
|
||||
jobs::{JobPayload, RawCode},
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
|
||||
fn bun_code(code: &str) -> RawCode {
|
||||
RawCode {
|
||||
hash: None,
|
||||
content: code.to_string(),
|
||||
path: None,
|
||||
language: ScriptLang::Bun,
|
||||
lock: None,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings:
|
||||
windmill_common::runnable_settings::ConcurrencySettings::default().into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_simple_script(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
"export function main() { return 42; }",
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!(42)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_script_with_args(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
"export function main(x: number, y: number) { return x + y; }",
|
||||
)))
|
||||
.arg("x", json!(10))
|
||||
.arg("y", json!(32))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!(42)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_script_with_logs(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
r#"export function main() {
|
||||
console.log("hello from agent worker");
|
||||
console.log("processing step 1");
|
||||
console.log("processing step 2");
|
||||
return "done";
|
||||
}"#,
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!("done")));
|
||||
|
||||
let logs = sqlx::query_scalar::<_, String>(
|
||||
"SELECT logs FROM job_logs WHERE job_id = $1 AND workspace_id = 'test-workspace'",
|
||||
)
|
||||
.bind(result.id)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
let logs = logs.expect("logs should exist");
|
||||
assert!(
|
||||
logs.contains("hello from agent worker"),
|
||||
"logs should contain the printed output, got: {logs}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_script_failure(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
"export function main() { throw new Error('test error'); }",
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(!result.success, "job should fail");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_complex_result(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(
|
||||
r#"export function main() {
|
||||
return {
|
||||
items: [1, 2, 3],
|
||||
metadata: { key: "value" },
|
||||
count: 3,
|
||||
};
|
||||
}"#,
|
||||
)))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job should succeed");
|
||||
let json = result.json_result().unwrap();
|
||||
assert_eq!(json["items"], json!([1, 2, 3]));
|
||||
assert_eq!(json["metadata"]["key"], json!("value"));
|
||||
assert_eq!(json["count"], json!(3));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_token_creation(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (client, _port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
// client.baseurl() already includes /api
|
||||
let resp = client
|
||||
.client()
|
||||
.post(format!(
|
||||
"{}/agent_workers/create_agent_token",
|
||||
client.baseurl()
|
||||
))
|
||||
.json(&json!({
|
||||
"worker_group": "lifecycle-test",
|
||||
"tags": ["bun", "flow", "dependency"],
|
||||
"exp": usize::MAX
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"create_agent_token should succeed, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
let token = resp.text().await?;
|
||||
let token = token.trim_matches('"');
|
||||
assert!(
|
||||
token.starts_with("jwt_agent_"),
|
||||
"token should start with jwt_agent_ prefix, got: {token}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_token_and_ping(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
let resp = client
|
||||
.client()
|
||||
.post(format!(
|
||||
"{}/agent_workers/create_agent_token",
|
||||
client.baseurl()
|
||||
))
|
||||
.json(&json!({
|
||||
"worker_group": "lifecycle-test",
|
||||
"tags": ["bun", "flow", "dependency"],
|
||||
"exp": usize::MAX
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(resp.status().is_success());
|
||||
let token = resp.text().await?;
|
||||
let token = token.trim_matches('"');
|
||||
|
||||
let suffix = windmill_common::utils::create_default_worker_suffix("lifecycle-test");
|
||||
let base_url = format!("http://localhost:{port}");
|
||||
let http_client =
|
||||
windmill_common::agent_workers::build_agent_http_client(&suffix, &token, &base_url);
|
||||
|
||||
// Initial ping inserts the worker record into the database
|
||||
let resp = http_client
|
||||
.client
|
||||
.post(format!("{}/api/agent_workers/update_ping", base_url))
|
||||
.json(&json!({
|
||||
"worker_instance": "test-instance",
|
||||
"ip": "127.0.0.1",
|
||||
"tags": ["bun"],
|
||||
"version": "test",
|
||||
"vcpus": 4,
|
||||
"memory": 8192,
|
||||
"ping_type": "Initial"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"initial ping should succeed, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
// Verify the ping was recorded in the database
|
||||
let worker_count = sqlx::query_scalar::<_, i64>(
|
||||
"SELECT COUNT(*) FROM worker_ping WHERE worker_instance = 'test-instance'",
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?;
|
||||
|
||||
assert!(worker_count > 0, "worker ping should be recorded in database");
|
||||
|
||||
// MainLoop ping updates the existing record
|
||||
let resp = http_client
|
||||
.client
|
||||
.post(format!("{}/api/agent_workers/update_ping", base_url))
|
||||
.json(&json!({
|
||||
"tags": ["bun"],
|
||||
"vcpus": 4,
|
||||
"memory": 8192,
|
||||
"jobs_executed": 0,
|
||||
"ping_type": "MainLoop"
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
resp.status().is_success(),
|
||||
"main loop ping should succeed, got: {}",
|
||||
resp.status()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_agent_worker_multiple_jobs_sequential(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let (_client, port, _server) = init_client_agent_mode(db.clone()).await;
|
||||
|
||||
for i in 0..3 {
|
||||
let result = RunJob::from(JobPayload::Code(bun_code(&format!(
|
||||
"export function main() {{ return {i}; }}"
|
||||
))))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
|
||||
assert!(result.success, "job {i} should succeed");
|
||||
assert_eq!(result.json_result(), Some(json!(i)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -838,7 +838,7 @@ pub async fn testing_http_connection(port: u16) -> Connection {
|
||||
"{}{}",
|
||||
windmill_common::agent_workers::AGENT_JWT_PREFIX,
|
||||
windmill_common::jwt::encode_with_internal_secret(
|
||||
windmill_api::agent_workers_ee::AgentAuth {
|
||||
windmill_api_agent_workers::AgentAuth {
|
||||
worker_group: "testing-agent".to_owned(),
|
||||
suffix: Some(suffix.clone()),
|
||||
tags: vec!["flow".into(), "python3".into(), "dependency".into()],
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "windmill-alerting"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_alerting"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,208 @@
|
||||
use axum::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
use windmill_common::error::{self, JsonResult};
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::DB;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct CriticalAlert {
|
||||
id: i32,
|
||||
alert_type: String,
|
||||
message: String,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
acknowledged: Option<bool>,
|
||||
workspace_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AlertQueryParams {
|
||||
pub page: Option<i32>,
|
||||
pub page_size: Option<i32>,
|
||||
pub acknowledged: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn get_critical_alerts(
|
||||
db: DB,
|
||||
params: AlertQueryParams,
|
||||
workspace_id: Option<String>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
let page = params.page.unwrap_or(1).max(1);
|
||||
let page_size = params.page_size.unwrap_or(10).min(100) as i64;
|
||||
let offset = ((page - 1) * page_size as i32) as i64;
|
||||
|
||||
let total_rows = if let Some(workspace_id) = &workspace_id {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2",
|
||||
workspace_id,
|
||||
params.acknowledged
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts"
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE COALESCE(acknowledged, false) = $1",
|
||||
params.acknowledged
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
let alerts = if let Some(workspace_id) = workspace_id {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
workspace_id,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $3 OFFSET $4",
|
||||
workspace_id,
|
||||
params.acknowledged,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2",
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
WHERE COALESCE(acknowledged, false) = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
params.acknowledged,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
let total_rows = total_rows.unwrap_or(0);
|
||||
let total_pages = ((total_rows as f64) / (page_size as f64)).ceil() as i64;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"alerts": alerts,
|
||||
"total_rows": total_rows,
|
||||
"total_pages": total_pages
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn acknowledge_critical_alert(
|
||||
db: DB,
|
||||
workspace_id: Option<String>,
|
||||
id: i32,
|
||||
) -> error::Result<String> {
|
||||
sqlx::query!(
|
||||
"UPDATE alerts
|
||||
SET
|
||||
acknowledged = true,
|
||||
acknowledged_workspace = CASE
|
||||
WHEN $3 THEN
|
||||
CASE
|
||||
WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true
|
||||
ELSE acknowledged_workspace
|
||||
END
|
||||
ELSE true
|
||||
END
|
||||
WHERE id = $1",
|
||||
id,
|
||||
workspace_id,
|
||||
*CLOUD_HOSTED
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
"Acknowledged critical alert with id: {}{}",
|
||||
id,
|
||||
workspace_id.map_or_else(|| "".to_string(), |w| format!(" for workspace_id: {}", w))
|
||||
);
|
||||
Ok("Critical alert acknowledged".to_string())
|
||||
}
|
||||
|
||||
pub async fn acknowledge_all_critical_alerts(
|
||||
db: DB,
|
||||
workspace_id: Option<String>,
|
||||
) -> error::Result<String> {
|
||||
sqlx::query!(
|
||||
"UPDATE alerts
|
||||
SET
|
||||
acknowledged = true,
|
||||
acknowledged_workspace = CASE
|
||||
WHEN $2 THEN
|
||||
CASE
|
||||
WHEN $1::text IS NOT NULL THEN true
|
||||
ELSE acknowledged_workspace
|
||||
END
|
||||
ELSE true
|
||||
END
|
||||
WHERE ($1::text IS NOT NULL AND workspace_id = $1)
|
||||
OR ($1::text IS NULL)",
|
||||
workspace_id,
|
||||
*CLOUD_HOSTED
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
"Acknowledged all unacknowledged critical alerts{}",
|
||||
workspace_id.map_or_else(|| "".to_string(), |w| format!(" for workspace_id: {}", w))
|
||||
);
|
||||
Ok("All unacknowledged critical alerts acknowledged".to_string())
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "windmill-api-agent-workers"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_agent_workers"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = ["windmill-common/enterprise", "windmill-queue/enterprise"]
|
||||
private = ["windmill-common/private", "windmill-queue/private"]
|
||||
python = ["dep:windmill-parser-py-imports"]
|
||||
benchmark = []
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-queue.workspace = true
|
||||
windmill-worker.workspace = true
|
||||
axum.workspace = true
|
||||
lazy_static.workspace = true
|
||||
chrono.workspace = true
|
||||
http.workspace = true
|
||||
hyper.workspace = true
|
||||
quick_cache.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
windmill-parser-py-imports = { workspace = true, optional = true }
|
||||
+4
-2
@@ -1,6 +1,8 @@
|
||||
#[cfg(feature = "private")]
|
||||
mod ee;
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::agent_workers_ee::*;
|
||||
pub use ee::*;
|
||||
|
||||
/*
|
||||
* Author: Ruben Fiszel
|
||||
@@ -11,7 +13,7 @@ pub use crate::agent_workers_ee::*;
|
||||
*/
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::db::DB;
|
||||
use windmill_common::DB;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use axum::Router;
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "windmill-api-assets"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_assets"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
@@ -12,7 +12,7 @@ use windmill_common::{
|
||||
error::JsonResult,
|
||||
};
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "windmill-api-configs"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_configs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = ["dep:windmill-autoscaling"]
|
||||
private = []
|
||||
python = []
|
||||
inline_preview = ["dep:windmill-worker", "dep:itertools"]
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-audit.workspace = true
|
||||
windmill-autoscaling = { workspace = true, optional = true }
|
||||
windmill-worker = { workspace = true, optional = true }
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
itertools = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
@@ -23,7 +23,7 @@ use windmill_common::{
|
||||
DB,
|
||||
};
|
||||
|
||||
use crate::{db::ApiAuthed, utils::require_devops_role};
|
||||
use windmill_api_auth::{ApiAuthed, require_devops_role};
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "windmill-api-debug"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_debug"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-audit.workspace = true
|
||||
axum.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
ed25519-dalek.workspace = true
|
||||
hex.workspace = true
|
||||
lazy_static.workspace = true
|
||||
rand.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
@@ -41,7 +41,7 @@ use windmill_common::{
|
||||
users::username_to_permissioned_as,
|
||||
};
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
|
||||
/// TTL for debug tokens in seconds (60 seconds)
|
||||
pub const DEBUG_TOKEN_TTL_SECS: i64 = 60;
|
||||
@@ -6,6 +6,8 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
||||
use windmill_common::DEFAULT_HUB_BASE_URL;
|
||||
#[cfg(feature = "embedding")]
|
||||
use windmill_common::HUB_BASE_URL;
|
||||
#[cfg(feature = "embedding")]
|
||||
use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
|
||||
use axum::Router;
|
||||
|
||||
@@ -54,13 +56,6 @@ lazy_static::lazy_static! {
|
||||
pub static ref EMBEDDINGS_DB: Arc<RwLock<Option<EmbeddingsDb>>> = Arc::new(RwLock::new(None));
|
||||
pub static ref MODEL_INSTANCE: Arc<RwLock<Option<Arc<ModelInstance>>>> = Arc::new(RwLock::new(None));
|
||||
pub static ref HUB_EMBEDDINGS_PULLING_INTERVAL_SECS: u64 = std::env::var("HUB_EMBEDDINGS_PULLING_INTERVAL_SECS").ok().map(|x| x.parse::<u64>().ok()).flatten().unwrap_or(3600 * 24);
|
||||
// Match windmill-api's HTTP_CLIENT config: 30s timeout, ACCEPT_INVALID_CERTS support
|
||||
static ref HTTP_CLIENT: reqwest::Client = windmill_common::utils::configure_client(reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.connect_timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.danger_accept_invalid_certs(std::env::var("ACCEPT_INVALID_CERTS").is_ok()))
|
||||
.build().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "embedding")]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_flow_conversations"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
sql-builder.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
+1
-1
@@ -9,7 +9,7 @@ use sql_builder::prelude::*;
|
||||
use sqlx::{FromRow, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
pub use windmill_common::flow_conversations::FlowConversation;
|
||||
use windmill_common::{
|
||||
db::{UserDB, DB},
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "windmill-api-inputs"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_inputs"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sql-builder.workspace = true
|
||||
sqlx.workspace = true
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
use axum::{
|
||||
extract::{Path, Query},
|
||||
routing::{get, post},
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_npm_proxy"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
flate2.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
tar.workspace = true
|
||||
tower-http.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -18,7 +18,8 @@ use windmill_common::{
|
||||
utils::StripPath,
|
||||
};
|
||||
|
||||
use crate::{db::ApiAuthed, HTTP_CLIENT};
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ProxyQuery {
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "windmill-api-openapi"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_openapi"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-store.workspace = true
|
||||
windmill-trigger-http.workspace = true
|
||||
anyhow.workspace = true
|
||||
axum.workspace = true
|
||||
http.workspace = true
|
||||
indexmap.workspace = true
|
||||
itertools.workspace = true
|
||||
lazy_static.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yml.workspace = true
|
||||
sqlx.workspace = true
|
||||
url.workspace = true
|
||||
@@ -22,11 +22,9 @@ use windmill_common::{
|
||||
};
|
||||
use windmill_store::resources::try_get_resource_from_db_as;
|
||||
|
||||
use crate::{
|
||||
db::ApiAuthed,
|
||||
triggers::http::{
|
||||
http_trigger_auth::ApiKeyAuthentication, AuthenticationMethod, HttpMethod, RequestType,
|
||||
},
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
use windmill_trigger_http::{
|
||||
http_trigger_auth::ApiKeyAuthentication, AuthenticationMethod, HttpMethod, RequestType,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "windmill-api-schedule"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_schedule"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = []
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-api-settings.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-audit.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-queue.workspace = true
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sql-builder.workspace = true
|
||||
sqlx.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -6,12 +6,8 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
settings::{delete_global_setting, set_global_setting_internal},
|
||||
users::maybe_refresh_folders,
|
||||
utils::{check_scopes, require_super_admin},
|
||||
};
|
||||
use windmill_api_auth::{check_scopes, maybe_refresh_folders, require_super_admin, ApiAuthed};
|
||||
use windmill_common::DB;
|
||||
use axum::{
|
||||
extract::{Extension, Path, Query},
|
||||
routing::{delete, get, post},
|
||||
@@ -922,9 +918,9 @@ async fn set_default_error_handler(
|
||||
};
|
||||
|
||||
if let Some(value_content) = value {
|
||||
set_global_setting_internal(&db, key, value_content).await?;
|
||||
windmill_api_settings::set_global_setting_internal(&db, key, value_content).await?;
|
||||
} else {
|
||||
delete_global_setting(&db, key.as_str()).await?;
|
||||
windmill_api_settings::delete_global_setting(&db, key.as_str()).await?;
|
||||
}
|
||||
|
||||
if payload.override_existing {
|
||||
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "windmill-api-settings"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_settings"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = []
|
||||
private = ["windmill-common/private"]
|
||||
parquet = ["dep:object_store", "windmill-common/parquet"]
|
||||
license = ["dep:rsa"]
|
||||
|
||||
[dependencies]
|
||||
windmill-alerting.workspace = true
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
anyhow.workspace = true
|
||||
bytes.workspace = true
|
||||
chrono.workspace = true
|
||||
futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
uuid.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
rsa = { workspace = true, optional = true }
|
||||
object_store = { workspace = true, optional = true }
|
||||
@@ -0,0 +1,14 @@
|
||||
#[cfg(feature = "private")]
|
||||
#[allow(unused)]
|
||||
pub use crate::ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use anyhow::anyhow;
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn validate_license_key(
|
||||
_license_key: String,
|
||||
_db: Option<&windmill_common::DB>,
|
||||
) -> anyhow::Result<(String, bool)> {
|
||||
// Implementation is not open source
|
||||
Err(anyhow!("License can't be validated in Windmill CE"))
|
||||
}
|
||||
@@ -8,12 +8,18 @@
|
||||
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
ee_oss::validate_license_key,
|
||||
utils::{generate_instance_username_for_all_users, require_super_admin},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
#[cfg(feature = "private")]
|
||||
mod ee;
|
||||
pub mod ee_oss;
|
||||
|
||||
use windmill_api_auth::{require_super_admin, ApiAuthed};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_api_auth::require_devops_role;
|
||||
use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
use windmill_common::DB;
|
||||
|
||||
use ee_oss::validate_license_key;
|
||||
use windmill_common::usernames::generate_instance_username_for_all_users;
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Path},
|
||||
@@ -25,9 +31,6 @@ use axum::{
|
||||
use axum::extract::Query;
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::utils::require_devops_role;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::ee_oss::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel};
|
||||
@@ -575,11 +578,11 @@ pub async fn test_critical_channels() -> Result<String> {
|
||||
pub async fn get_critical_alerts(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
Query(params): Query<crate::utils::AlertQueryParams>,
|
||||
Query(params): Query<windmill_alerting::AlertQueryParams>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
crate::utils::get_critical_alerts(db, params, None).await
|
||||
windmill_alerting::get_critical_alerts(db, params, None).await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -594,7 +597,7 @@ pub async fn acknowledge_critical_alert(
|
||||
Path(id): Path<i32>,
|
||||
) -> error::Result<String> {
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
crate::utils::acknowledge_critical_alert(db, None, id).await
|
||||
windmill_alerting::acknowledge_critical_alert(db, None, id).await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -609,7 +612,7 @@ pub async fn acknowledge_all_critical_alerts(
|
||||
) -> error::Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
crate::utils::acknowledge_all_critical_alerts(db, None).await
|
||||
windmill_alerting::acknowledge_all_critical_alerts(db, None).await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "windmill-api-workers"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_workers"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
axum.workspace = true
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
uuid.workspace = true
|
||||
@@ -24,7 +24,7 @@ use windmill_common::{
|
||||
DB,
|
||||
};
|
||||
|
||||
use crate::{db::ApiAuthed, utils::require_super_admin};
|
||||
use windmill_api_auth::{ApiAuthed, require_super_admin};
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
@@ -10,15 +10,15 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"]
|
||||
private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"]
|
||||
stripe = []
|
||||
inline_preview = ["dep:windmill-worker"]
|
||||
agent_worker_server = ["dep:windmill-worker"]
|
||||
inline_preview = ["dep:windmill-worker", "windmill-api-configs/inline_preview"]
|
||||
agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"]
|
||||
enterprise_saml = ["dep:samael", "dep:libxml"]
|
||||
benchmark = []
|
||||
embedding = ["windmill-api-embeddings/embedding"]
|
||||
parquet = ["dep:datafusion", "dep:object_store", "windmill-common/parquet", "windmill-worker?/parquet", "windmill-api-users/parquet", "dep:aws-sigv4", "dep:aws-sdk-config"]
|
||||
parquet = ["dep:datafusion", "dep:object_store", "windmill-common/parquet", "windmill-worker?/parquet", "windmill-api-users/parquet", "windmill-api-settings/parquet", "dep:aws-sigv4", "dep:aws-sdk-config"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker?/prometheus"]
|
||||
openidconnect = ["dep:openidconnect", "windmill-common/openidconnect", "windmill-store/openidconnect"]
|
||||
tantivy = ["dep:windmill-indexer"]
|
||||
@@ -27,10 +27,10 @@ kafka-gssapi = ["kafka", "windmill-trigger-kafka/kafka-gssapi"]
|
||||
nats = ["dep:windmill-trigger-nats", "windmill-store/nats"]
|
||||
websocket = ["dep:windmill-trigger-websocket"]
|
||||
smtp = ["dep:mail-parser", "dep:openssl", "windmill-common/smtp", "dep:windmill-trigger-email"]
|
||||
license = ["dep:rsa"]
|
||||
license = ["dep:rsa", "windmill-api-settings/license"]
|
||||
zip = ["dep:async_zip"]
|
||||
oauth2 = ["dep:windmill-oauth", "windmill-store/oauth2"]
|
||||
http_trigger = ["dep:matchit", "dep:windmill-trigger-http", "windmill-store/http_trigger"]
|
||||
http_trigger = ["dep:matchit", "dep:windmill-trigger-http", "windmill-store/http_trigger", "dep:windmill-api-openapi"]
|
||||
static_frontend = ["dep:rust-embed"]
|
||||
postgres_trigger = ["dep:windmill-trigger-postgres", "windmill-store/postgres_trigger"]
|
||||
mqtt_trigger = ["dep:windmill-trigger-mqtt", "windmill-store/mqtt_trigger"]
|
||||
@@ -40,7 +40,7 @@ gcp_trigger = ["dep:windmill-trigger-gcp", "windmill-store/gcp_trigger"]
|
||||
cloud = ["windmill-common/cloud", "windmill-api-auth/cloud", "windmill-store/cloud", "windmill-api-workspaces/cloud"]
|
||||
mcp = ["dep:windmill-mcp", "windmill-mcp/server", "windmill-mcp/auth", "windmill-api-auth/mcp", "windmill-store/mcp"]
|
||||
bedrock = ["dep:aws-sdk-bedrock", "dep:aws-sdk-bedrockruntime", "windmill-common/bedrock", "dep:aws-config", "dep:aws-credential-types", "dep:aws-smithy-types"]
|
||||
python = ["windmill-dep-map/python", "dep:windmill-parser-py", "dep:windmill-parser-py-imports", "windmill-api-scripts/python", "windmill-trigger/python", "windmill-common/python"]
|
||||
python = ["windmill-dep-map/python", "dep:windmill-parser-py", "dep:windmill-parser-py-imports", "windmill-api-scripts/python", "windmill-api-configs/python", "windmill-api-agent-workers?/python", "windmill-trigger/python", "windmill-common/python"]
|
||||
no_auth = ["windmill-api-auth/no_auth", "windmill-store/no_auth", "windmill-api-users/no_auth"]
|
||||
quickjs = ["windmill-jseval/quickjs"]
|
||||
|
||||
@@ -142,7 +142,19 @@ windmill-trigger-sqs = { workspace = true, optional = true }
|
||||
windmill-trigger-gcp = { workspace = true, optional = true }
|
||||
windmill-trigger-http = { workspace = true, optional = true }
|
||||
windmill-native-triggers = { workspace = true, optional = true }
|
||||
windmill-alerting.workspace = true
|
||||
windmill-api-agent-workers = { workspace = true, optional = true }
|
||||
windmill-api-assets.workspace = true
|
||||
windmill-api-configs = { workspace = true }
|
||||
windmill-api-debug.workspace = true
|
||||
windmill-api-embeddings.workspace = true
|
||||
windmill-api-flow-conversations.workspace = true
|
||||
windmill-api-inputs.workspace = true
|
||||
windmill-api-npm-proxy.workspace = true
|
||||
windmill-api-openapi = { workspace = true, optional = true }
|
||||
windmill-api-schedule.workspace = true
|
||||
windmill-api-settings = { workspace = true }
|
||||
windmill-api-workers.workspace = true
|
||||
const_format.workspace = true
|
||||
pin-project.workspace = true
|
||||
http.workspace = true
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::{
|
||||
webhook_util::WebhookShared,
|
||||
};
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
use agent_workers_oss::AgentCache;
|
||||
use windmill_api_agent_workers::AgentCache;
|
||||
|
||||
use anyhow::Context;
|
||||
use argon2::Argon2;
|
||||
@@ -39,7 +39,6 @@ use axum::http::HeaderValue;
|
||||
use axum::response::Response;
|
||||
use axum::{middleware::from_extractor, routing::get, routing::post, Extension, Json, Router};
|
||||
use db::DB;
|
||||
use reqwest::Client;
|
||||
use tokio::task::JoinHandle;
|
||||
use windmill_common::global_settings::load_value_from_global_settings;
|
||||
use windmill_common::global_settings::EMAIL_DOMAIN_SETTING;
|
||||
@@ -60,21 +59,16 @@ use windmill_common::worker::CLOUD_HOSTED;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use windmill_common::BASE_URL;
|
||||
use windmill_common::{
|
||||
utils::{configure_client, GIT_VERSION},
|
||||
utils::GIT_VERSION,
|
||||
INSTANCE_NAME,
|
||||
};
|
||||
|
||||
use crate::scim_oss::has_scim_token;
|
||||
use windmill_common::error::AppError;
|
||||
|
||||
#[cfg(all(feature = "agent_worker_server", feature = "private"))]
|
||||
pub mod agent_workers_ee;
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
mod agent_workers_oss;
|
||||
mod ai;
|
||||
mod apps;
|
||||
pub mod args;
|
||||
mod assets;
|
||||
mod audit;
|
||||
pub mod auth;
|
||||
#[cfg(all(feature = "private", feature = "parquet"))]
|
||||
@@ -84,16 +78,14 @@ mod azure_proxy_oss;
|
||||
mod bedrock;
|
||||
mod capture;
|
||||
mod concurrency_groups;
|
||||
mod configs;
|
||||
mod db;
|
||||
pub mod debug;
|
||||
|
||||
mod drafts;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod ee;
|
||||
pub mod ee_oss;
|
||||
pub mod embeddings;
|
||||
mod favorite;
|
||||
mod flow_conversations;
|
||||
pub mod flows;
|
||||
mod folder_history;
|
||||
mod folders;
|
||||
@@ -106,12 +98,8 @@ mod indexer_oss;
|
||||
#[cfg(feature = "private")]
|
||||
mod inkeep_ee;
|
||||
mod inkeep_oss;
|
||||
mod inputs;
|
||||
mod integration;
|
||||
mod live_migrations;
|
||||
mod npm_proxy;
|
||||
#[cfg(feature = "http_trigger")]
|
||||
mod openapi;
|
||||
#[cfg(all(feature = "private", feature = "parquet"))]
|
||||
pub mod s3_proxy_ee;
|
||||
mod s3_proxy_oss;
|
||||
@@ -145,14 +133,12 @@ mod resources;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod saml_ee;
|
||||
mod saml_oss;
|
||||
mod schedule;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod scim_ee;
|
||||
mod scim_oss;
|
||||
mod scripts;
|
||||
mod secret_backend_ext;
|
||||
mod service_logs;
|
||||
mod settings;
|
||||
mod slack_approvals;
|
||||
#[cfg(all(feature = "smtp", feature = "private"))]
|
||||
pub mod smtp_server_ee;
|
||||
@@ -187,7 +173,6 @@ mod users_oss;
|
||||
mod utils;
|
||||
mod variables;
|
||||
pub mod webhook_util;
|
||||
mod workers;
|
||||
mod workspaces;
|
||||
#[cfg(feature = "private")]
|
||||
pub mod workspaces_ee;
|
||||
@@ -215,17 +200,10 @@ lazy_static::lazy_static! {
|
||||
|
||||
|
||||
// COOKIE_DOMAIN and IS_SECURE are now in windmill_common::utils
|
||||
|
||||
pub static ref HTTP_CLIENT: Client = configure_client(reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(30))
|
||||
.danger_accept_invalid_certs(std::env::var("ACCEPT_INVALID_CERTS").is_ok()))
|
||||
.build().unwrap();
|
||||
|
||||
|
||||
}
|
||||
|
||||
pub use windmill_common::utils::HTTP_CLIENT_PERMISSIVE as HTTP_CLIENT;
|
||||
|
||||
pub use windmill_common::utils::{COOKIE_DOMAIN, IS_SECURE};
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
@@ -304,7 +282,7 @@ pub async fn run_server(
|
||||
let argon2 = Arc::new(Argon2::default());
|
||||
|
||||
// Initialize debug signing key for debugger authentication
|
||||
debug::init_debug_signing_key().await;
|
||||
windmill_api_debug::init_debug_signing_key().await;
|
||||
|
||||
let disable_response_logs = std::env::var("DISABLE_RESPONSE_LOGS")
|
||||
.ok()
|
||||
@@ -444,7 +422,7 @@ pub async fn run_server(
|
||||
#[cfg(feature = "agent_worker_server")]
|
||||
let (agent_workers_router, agent_workers_bg_processor, agent_workers_job_completed_tx) =
|
||||
if server_mode {
|
||||
agent_workers_oss::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
windmill_api_agent_workers::workspaced_service(db.clone(), _base_internal_url.clone())
|
||||
} else {
|
||||
(Router::new(), vec![], None)
|
||||
};
|
||||
@@ -463,7 +441,7 @@ pub async fn run_server(
|
||||
// Reordered alphabetically
|
||||
.nest("/acls", granular_acls::workspaced_service())
|
||||
.nest("/apps", apps::workspaced_service())
|
||||
.nest("/assets", assets::workspaced_service())
|
||||
.nest("/assets", windmill_api_assets::workspaced_service())
|
||||
.nest("/audit", audit::workspaced_service())
|
||||
.nest("/capture", capture::workspaced_service())
|
||||
.nest(
|
||||
@@ -480,17 +458,17 @@ pub async fn run_server(
|
||||
)
|
||||
.nest(
|
||||
"/flow_conversations",
|
||||
flow_conversations::workspaced_service(),
|
||||
windmill_api_flow_conversations::workspaced_service(),
|
||||
)
|
||||
.nest("/folders", folders::workspaced_service())
|
||||
.nest("/folders_history", folder_history::workspaced_service())
|
||||
.nest("/groups", groups::workspaced_service())
|
||||
.nest("/groups_history", group_history::workspaced_service())
|
||||
.nest("/inputs", inputs::workspaced_service())
|
||||
.nest("/inputs", windmill_api_inputs::workspaced_service())
|
||||
.nest("/job_metrics", job_metrics::workspaced_service())
|
||||
.nest("/job_helpers", job_helpers_service)
|
||||
.nest("/jobs", jobs::workspaced_service())
|
||||
.nest("/debug", debug::workspaced_service())
|
||||
.nest("/debug", windmill_api_debug::workspaced_service())
|
||||
.nest("/native_triggers", {
|
||||
#[cfg(feature = "native_trigger")]
|
||||
{
|
||||
@@ -523,23 +501,23 @@ pub async fn run_server(
|
||||
Router::new()
|
||||
})
|
||||
.nest("/ai", ai::workspaced_service())
|
||||
.nest("/npm_proxy", npm_proxy::workspaced_service())
|
||||
.nest("/npm_proxy", windmill_api_npm_proxy::workspaced_service())
|
||||
.nest("/raw_apps", raw_apps::workspaced_service())
|
||||
.nest("/resources", resources::workspaced_service())
|
||||
.nest("/schedules", schedule::workspaced_service())
|
||||
.nest("/schedules", windmill_api_schedule::workspaced_service())
|
||||
.nest("/scripts", scripts::workspaced_service())
|
||||
.nest(
|
||||
"/users",
|
||||
users::workspaced_service().layer(Extension(argon2.clone())),
|
||||
)
|
||||
.nest("/variables", variables::workspaced_service())
|
||||
.nest("/workers", workers::workspaced_service())
|
||||
.nest("/workers", windmill_api_workers::workspaced_service())
|
||||
.nest("/workspaces", workspaces::workspaced_service())
|
||||
.nest("/oidc", oidc_oss::workspaced_service())
|
||||
.nest("/openapi", {
|
||||
#[cfg(feature = "http_trigger")]
|
||||
{
|
||||
openapi::openapi_service()
|
||||
windmill_api_openapi::openapi_service()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "http_trigger"))]
|
||||
@@ -554,16 +532,16 @@ pub async fn run_server(
|
||||
"/users",
|
||||
users::global_service().layer(Extension(argon2.clone())),
|
||||
)
|
||||
.nest("/settings", settings::global_service())
|
||||
.nest("/workers", workers::global_service())
|
||||
.nest("/settings", windmill_api_settings::global_service())
|
||||
.nest("/workers", windmill_api_workers::global_service())
|
||||
.nest("/service_logs", service_logs::global_service())
|
||||
.nest("/configs", configs::global_service())
|
||||
.nest("/configs", windmill_api_configs::global_service())
|
||||
.nest("/scripts", scripts::global_service())
|
||||
.nest("/integrations", integration::global_service())
|
||||
.nest("/groups", groups::global_service())
|
||||
.nest("/flows", flows::global_service())
|
||||
.nest("/apps", apps::global_service().layer(cors.clone()))
|
||||
.nest("/schedules", schedule::global_service())
|
||||
.nest("/schedules", windmill_api_schedule::global_service())
|
||||
.nest("/embeddings", embeddings::global_service())
|
||||
.nest("/ai", ai::global_service())
|
||||
.nest("/inkeep", inkeep_oss::global_service())
|
||||
@@ -588,7 +566,7 @@ pub async fn run_server(
|
||||
)
|
||||
.nest("/srch/index", indexer_oss::global_service())
|
||||
.nest("/oidc", oidc_oss::global_service())
|
||||
.nest("/debug", debug::global_service())
|
||||
.nest("/debug", windmill_api_debug::global_service())
|
||||
.nest(
|
||||
"/saml",
|
||||
saml_oss::global_service().layer(Extension(Arc::clone(&sp_extension))),
|
||||
@@ -628,7 +606,7 @@ pub async fn run_server(
|
||||
if let Some(agent_workers_job_completed_tx) =
|
||||
agent_workers_job_completed_tx.clone()
|
||||
{
|
||||
agent_workers_oss::global_service(agent_workers_job_completed_tx)
|
||||
windmill_api_agent_workers::global_service(agent_workers_job_completed_tx)
|
||||
.layer(Extension(agent_cache.clone()))
|
||||
} else {
|
||||
Router::new()
|
||||
@@ -800,7 +778,7 @@ pub async fn run_server(
|
||||
},
|
||||
)
|
||||
// JWKS endpoint for HashiCorp Vault JWT authentication (must be outside /api prefix)
|
||||
.route("/.well-known/jwks.json", get(settings::get_jwks))
|
||||
.route("/.well-known/jwks.json", get(windmill_api_settings::get_jwks))
|
||||
.fallback(static_assets::static_handler)
|
||||
.layer(middleware_stack);
|
||||
|
||||
|
||||
@@ -8,61 +8,18 @@
|
||||
|
||||
use axum::{body::Body, response::Response};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::{
|
||||
error::{self},
|
||||
DB,
|
||||
};
|
||||
|
||||
pub use windmill_api_auth::{check_scopes, require_devops_role, require_super_admin};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use windmill_common::error::JsonResult;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::Json;
|
||||
|
||||
#[cfg(feature = "private")]
|
||||
pub use windmill_common::usernames::generate_instance_wide_unique_username;
|
||||
pub use windmill_common::utils::WithStarredInfoQuery;
|
||||
|
||||
pub async fn generate_instance_username_for_all_users(db: &DB) -> error::Result<()> {
|
||||
let mut tx = db.begin().await?;
|
||||
// get users that have a no instance username and either 1 or 0 workspace usernames
|
||||
let users = sqlx::query!(r#"SELECT p.email as "email!", u.username as "username?" FROM password p LEFT JOIN usr u ON p.email = u.email WHERE p.username IS NULL AND (SELECT COUNT(DISTINCT username) FROM usr WHERE email = p.email) <= 1"#)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
for user in users {
|
||||
let username = if let Some(username) = user.username {
|
||||
// if has workspace username, check that username is unique
|
||||
let username_conflict = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 and email != $2 UNION SELECT 1 FROM password WHERE username = $1 UNION SELECT 1 FROM pending_user WHERE username = $1)",
|
||||
&username,
|
||||
&user.email
|
||||
).fetch_one(&mut *tx).await?.unwrap_or(false);
|
||||
|
||||
if !username_conflict {
|
||||
username
|
||||
} else {
|
||||
generate_instance_wide_unique_username(&mut tx, &user.email).await?
|
||||
}
|
||||
} else {
|
||||
generate_instance_wide_unique_username(&mut tx, &user.email).await?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE password SET username = $1 WHERE email = $2",
|
||||
&username,
|
||||
&user.email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub use windmill_alerting::{
|
||||
acknowledge_all_critical_alerts, acknowledge_critical_alert, get_critical_alerts,
|
||||
AlertQueryParams,
|
||||
};
|
||||
|
||||
pub fn content_plain(body: Body) -> Response {
|
||||
use axum::http::header;
|
||||
@@ -81,222 +38,6 @@ where
|
||||
Ok(o.filter(|s| !s.trim().is_empty()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct CriticalAlert {
|
||||
id: i32,
|
||||
alert_type: String,
|
||||
message: String,
|
||||
created_at: chrono::DateTime<chrono::Utc>,
|
||||
acknowledged: Option<bool>,
|
||||
workspace_id: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct AlertQueryParams {
|
||||
pub page: Option<i32>,
|
||||
pub page_size: Option<i32>,
|
||||
pub acknowledged: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn get_critical_alerts(
|
||||
db: DB,
|
||||
params: AlertQueryParams,
|
||||
workspace_id: Option<String>,
|
||||
) -> JsonResult<serde_json::Value> {
|
||||
// Returning total rows and total pages
|
||||
let page = params.page.unwrap_or(1).max(1);
|
||||
let page_size = params.page_size.unwrap_or(10).min(100) as i64;
|
||||
let offset = ((page - 1) * page_size as i32) as i64;
|
||||
|
||||
// Count total rows
|
||||
let total_rows = if let Some(workspace_id) = &workspace_id {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1",
|
||||
workspace_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2",
|
||||
workspace_id,
|
||||
params.acknowledged
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
if params.acknowledged.is_none() {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts"
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT COUNT(*)
|
||||
FROM alerts
|
||||
WHERE COALESCE(acknowledged, false) = $1",
|
||||
params.acknowledged
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch paginated rows
|
||||
let alerts = if let Some(workspace_id) = workspace_id {
|
||||
// `workspace_id` is provided => workspace admin
|
||||
if params.acknowledged.is_none() {
|
||||
// Case: return all rows where `workspace_id` matches
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
workspace_id,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
} else {
|
||||
// Case: return rows where `acknowledged_workspace` matches `params.acknowledged`
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged_workspace, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
WHERE workspace_id = $1 AND COALESCE(acknowledged_workspace, false) = $2
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $3 OFFSET $4",
|
||||
workspace_id,
|
||||
params.acknowledged,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
// `workspace_id` is not provided => superadmin
|
||||
if params.acknowledged.is_none() {
|
||||
// Case: Return all rows unfiltered with global acknowledged as acknowledged
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2",
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
} else {
|
||||
// Case: Return rows where global acknowledged matches params.acknowledged
|
||||
sqlx::query_as!(
|
||||
CriticalAlert,
|
||||
"SELECT id, alert_type, message, created_at, COALESCE(acknowledged, false) AS acknowledged, workspace_id
|
||||
FROM alerts
|
||||
WHERE COALESCE(acknowledged, false) = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
params.acknowledged,
|
||||
page_size,
|
||||
offset
|
||||
)
|
||||
.fetch_all(&db)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
let total_rows = total_rows.unwrap_or(0);
|
||||
let total_pages = ((total_rows as f64) / (page_size as f64)).ceil() as i64;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"alerts": alerts,
|
||||
"total_rows": total_rows,
|
||||
"total_pages": total_pages
|
||||
})))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn acknowledge_critical_alert(
|
||||
db: DB,
|
||||
workspace_id: Option<String>,
|
||||
id: i32,
|
||||
) -> error::Result<String> {
|
||||
sqlx::query!(
|
||||
"UPDATE alerts
|
||||
SET
|
||||
acknowledged = true,
|
||||
acknowledged_workspace = CASE
|
||||
WHEN $3 THEN
|
||||
CASE
|
||||
WHEN $2::text IS NOT NULL AND workspace_id = $2 THEN true
|
||||
ELSE acknowledged_workspace
|
||||
END
|
||||
ELSE true
|
||||
END
|
||||
WHERE id = $1",
|
||||
id,
|
||||
workspace_id,
|
||||
*CLOUD_HOSTED
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
"Acknowledged critical alert with id: {}{}",
|
||||
id,
|
||||
workspace_id.map_or_else(|| "".to_string(), |w| format!(" for workspace_id: {}", w))
|
||||
);
|
||||
Ok("Critical alert acknowledged".to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn acknowledge_all_critical_alerts(
|
||||
db: DB,
|
||||
workspace_id: Option<String>,
|
||||
) -> error::Result<String> {
|
||||
sqlx::query!(
|
||||
"UPDATE alerts
|
||||
SET
|
||||
acknowledged = true,
|
||||
acknowledged_workspace = CASE
|
||||
WHEN $2 THEN
|
||||
CASE
|
||||
WHEN $1::text IS NOT NULL THEN true
|
||||
ELSE acknowledged_workspace
|
||||
END
|
||||
ELSE true
|
||||
END
|
||||
WHERE ($1::text IS NOT NULL AND workspace_id = $1)
|
||||
OR ($1::text IS NULL)",
|
||||
workspace_id,
|
||||
*CLOUD_HOSTED
|
||||
)
|
||||
.execute(&db)
|
||||
.await?;
|
||||
|
||||
tracing::info!(
|
||||
"Acknowledged all unacknowledged critical alerts{}",
|
||||
workspace_id.map_or_else(|| "".to_string(), |w| format!(" for workspace_id: {}", w))
|
||||
);
|
||||
Ok("All unacknowledged critical alerts acknowledged".to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "http_trigger")]
|
||||
pub use windmill_common::utils::ExpiringCacheEntry;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use regex::Regex;
|
||||
use sqlx::{Postgres, Transaction};
|
||||
|
||||
use crate::error::{self, Error};
|
||||
use crate::DB;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref INVALID_USERNAME_CHARS: Regex = Regex::new(r"[^A-Za-z0-9_]").unwrap();
|
||||
@@ -57,6 +58,42 @@ pub async fn generate_instance_wide_unique_username<'c>(
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
pub async fn generate_instance_username_for_all_users(db: &DB) -> error::Result<()> {
|
||||
let mut tx = db.begin().await?;
|
||||
let users = sqlx::query!(r#"SELECT p.email as "email!", u.username as "username?" FROM password p LEFT JOIN usr u ON p.email = u.email WHERE p.username IS NULL AND (SELECT COUNT(DISTINCT username) FROM usr WHERE email = p.email) <= 1"#)
|
||||
.fetch_all(&mut *tx)
|
||||
.await?;
|
||||
|
||||
for user in users {
|
||||
let username = if let Some(username) = user.username {
|
||||
let username_conflict = sqlx::query_scalar!(
|
||||
"SELECT EXISTS(SELECT 1 FROM usr WHERE username = $1 and email != $2 UNION SELECT 1 FROM password WHERE username = $1 UNION SELECT 1 FROM pending_user WHERE username = $1)",
|
||||
&username,
|
||||
&user.email
|
||||
).fetch_one(&mut *tx).await?.unwrap_or(false);
|
||||
|
||||
if !username_conflict {
|
||||
username
|
||||
} else {
|
||||
generate_instance_wide_unique_username(&mut tx, &user.email).await?
|
||||
}
|
||||
} else {
|
||||
generate_instance_wide_unique_username(&mut tx, &user.email).await?
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE password SET username = $1 WHERE email = $2",
|
||||
&username,
|
||||
&user.email
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_instance_username_or_create_pending<'c>(
|
||||
tx: &mut Transaction<'c, Postgres>,
|
||||
email: &str,
|
||||
|
||||
@@ -71,6 +71,12 @@ lazy_static::lazy_static! {
|
||||
|
||||
builder.build().unwrap()
|
||||
};
|
||||
pub static ref HTTP_CLIENT_PERMISSIVE: Client = configure_client(reqwest::ClientBuilder::new()
|
||||
.user_agent("windmill/beta")
|
||||
.connect_timeout(std::time::Duration::from_secs(10))
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.danger_accept_invalid_certs(std::env::var("ACCEPT_INVALID_CERTS").is_ok()))
|
||||
.build().unwrap();
|
||||
pub static ref GIT_SEM_VERSION: Version = Version::parse(
|
||||
if GIT_VERSION.starts_with('v') {
|
||||
&GIT_VERSION[1..]
|
||||
|
||||
Reference in New Issue
Block a user