From f59b2fda6f96952fb8d6a210f9982cc77fb6a4f6 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 27 Nov 2025 19:22:33 +0000 Subject: [PATCH 01/19] handle incomplete bytes spread across chunks --- dwctl/src/api/handlers/files.rs | 93 ++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/dwctl/src/api/handlers/files.rs b/dwctl/src/api/handlers/files.rs index 2c6680a7c..3e6585174 100644 --- a/dwctl/src/api/handlers/files.rs +++ b/dwctl/src/api/handlers/files.rs @@ -129,6 +129,7 @@ fn create_file_stream( let mut total_size = 0i64; let mut line_count = 0u64; let mut incomplete_line = String::new(); + let mut incomplete_utf8_bytes = Vec::new(); // Buffer for incomplete UTF-8 sequences at chunk boundaries let mut metadata = fusillade::FileMetadata { uploaded_by, ..Default::default() @@ -191,17 +192,93 @@ fn create_file_stream( return; } - // Convert chunk to UTF-8 - let chunk_str = match std::str::from_utf8(&chunk) { - Ok(s) => s, - Err(_) => { - let _ = tx - .send(fusillade::FileStreamItem::Error("File contains invalid UTF-8".to_string())) - .await; - return; + // Combine incomplete UTF-8 bytes from previous chunk with current chunk + let combined_bytes = if incomplete_utf8_bytes.is_empty() { + chunk.to_vec() + } else { + let mut combined = incomplete_utf8_bytes.clone(); + combined.extend_from_slice(&chunk); + combined + }; + + // Try to convert to UTF-8, handling incomplete sequences at the end + let (chunk_str, remaining_bytes) = match std::str::from_utf8(&combined_bytes) { + Ok(s) => { + // All bytes are valid UTF-8 + incomplete_utf8_bytes.clear(); + (s.to_string(), Vec::new()) + } + Err(e) => { + // Check if the error is due to an incomplete sequence at the end + let valid_up_to = e.valid_up_to(); + + // If there's an error length, it means we have invalid UTF-8, not just incomplete + if let Some(error_len) = e.error_len() { + // This is actual invalid UTF-8, not just an incomplete sequence + tracing::error!( + "UTF-8 parsing error on/near line {}, byte offset {} in combined buffer, total file offset ~{}, combined buffer size: {} bytes, error: {:?}", + line_count + 1, + valid_up_to, + total_size - chunk_size + valid_up_to as i64, + combined_bytes.len(), + e + ); + + // Show a hex dump of the problematic area + let error_start = valid_up_to.saturating_sub(20); + let error_end = (valid_up_to + error_len + 20).min(combined_bytes.len()); + let problem_bytes = &combined_bytes[error_start..error_end]; + tracing::error!( + "Bytes around error (offset {}-{}): {:02x?}", + error_start, + error_end, + problem_bytes + ); + + // Try to show ASCII representation + let ascii_repr: String = problem_bytes.iter() + .map(|&b| if b.is_ascii_graphic() || b == b' ' { b as char } else { '.' }) + .collect(); + tracing::error!("ASCII representation: '{}'", ascii_repr); + + // Also show the incomplete_line if any + if !incomplete_line.is_empty() { + tracing::error!( + "Incomplete line from previous chunk (may be part of the problem): '{}'", + incomplete_line.chars().take(200).collect::() + ); + } + + let error_msg = format!( + "File contains invalid UTF-8 on/near line {} at byte offset {}. Error: {}", + line_count + 1, + total_size - chunk_size + valid_up_to as i64, + e + ); + let _ = tx + .send(fusillade::FileStreamItem::Error(error_msg)) + .await; + return; + } + + // Otherwise, this is an incomplete UTF-8 sequence at the end of the chunk + // Save the incomplete bytes for the next chunk + let valid_str = std::str::from_utf8(&combined_bytes[..valid_up_to]) + .expect("valid_up_to should point to valid UTF-8"); + let remaining = combined_bytes[valid_up_to..].to_vec(); + + tracing::debug!( + "Incomplete UTF-8 sequence at chunk boundary, buffering {} bytes for next chunk", + remaining.len() + ); + + (valid_str.to_string(), remaining) } }; + // Update the incomplete UTF-8 buffer for next iteration + incomplete_utf8_bytes = remaining_bytes; + // Combine with incomplete line from previous chunk let text_to_process = if incomplete_line.is_empty() { chunk_str.to_string() From 81f341462798cb318228588d567a65eb1d45532b Mon Sep 17 00:00:00 2001 From: Fergus Date: Fri, 28 Nov 2025 10:53:54 +0000 Subject: [PATCH 02/19] feat(deps): bump fusillade to 0.4.0 --- dwctl/Cargo.toml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dwctl/Cargo.toml b/dwctl/Cargo.toml index 54844a6ea..ef68c35da 100644 --- a/dwctl/Cargo.toml +++ b/dwctl/Cargo.toml @@ -20,7 +20,7 @@ test-utils = ["dep:axum-test"] [dependencies] axum = { version = "0.8", features = ["multipart"] } -fusillade = "0.3.0" +fusillade = "0.4.0" tokio = { version = "1.0", features = ["full"] } tokio-stream = { version = "0.1", features = ["sync"] } tokio-util = "0.7" @@ -103,7 +103,13 @@ axum-test = { version = "17.3", optional = true } dashmap = { version = "6.1.0", features = ["serde"] } moka = { version = "0.12", features = ["future"] } # Stripe payment processing -async-stripe = { version = "0.41.0", default-features = false, features = ["runtime-tokio-hyper", "checkout", "webhook-endpoints", "webhook-events", "connect"] } +async-stripe = { version = "0.41.0", default-features = false, features = [ + "runtime-tokio-hyper", + "checkout", + "webhook-endpoints", + "webhook-events", + "connect", +] } [dev-dependencies] axum-test = { version = "17.3" } From d63a356dd82e73dff9a96b0498d465710339165e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:38:21 +0000 Subject: [PATCH 03/19] chore(deps): update dependency vite to v7.2.4 (#263) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 681cea09e..2c2d76384 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -11419,9 +11419,9 @@ } }, "node_modules/vite": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", - "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", + "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "license": "MIT", "peer": true, "dependencies": { From d01956d33981c9c96cde120c89ca6fe4a371755d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:38:33 +0000 Subject: [PATCH 04/19] chore(deps): update react monorepo (#264) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 2c2d76384..689d76670 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -3644,19 +3644,19 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", - "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", "peer": true, "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", - "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -4943,9 +4943,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/d3-array": { From aa233c32eeb15f53efc26fd5c9058b74960d5bbb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:38:44 +0000 Subject: [PATCH 05/19] fix(deps): update dependency lucide-react to ^0.555.0 (#270) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 8 ++++---- dashboard/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 689d76670..6fad5907d 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -40,7 +40,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", - "lucide-react": "^0.540.0", + "lucide-react": "^0.555.0", "next-themes": "^0.4.6", "openai": "^5.13.1", "react": "^19.1.1", @@ -7807,9 +7807,9 @@ } }, "node_modules/lucide-react": { - "version": "0.540.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.540.0.tgz", - "integrity": "sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==", + "version": "0.555.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.555.0.tgz", + "integrity": "sha512-D8FvHUGbxWBRQM90NZeIyhAvkFfsh3u9ekrMvJ30Z6gnpBHS6HC6ldLg7tL45hwiIz/u66eKDtdA23gwwGsAHA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/dashboard/package.json b/dashboard/package.json index b2b0e13d7..0f241afb4 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -46,7 +46,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", - "lucide-react": "^0.540.0", + "lucide-react": "^0.555.0", "next-themes": "^0.4.6", "openai": "^5.13.1", "react": "^19.1.1", From 1719b775e38be646d6ebe5502f63a69fa5776f07 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:39:09 +0000 Subject: [PATCH 06/19] chore(deps): update rust docker tag to v1.91.1 (#269) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f5378ed28..c5d5b078c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Backend build stage -FROM rust:1.90.0-slim AS backend-builder +FROM rust:1.91.1-slim AS backend-builder # Install build dependencies including Node.js RUN apt-get update && apt-get install -y \ From 1c4f6facd4550c021c6e438238b1816d0f679082 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:39:17 +0000 Subject: [PATCH 07/19] chore(deps): update rust crate criterion to 0.7 (#268) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dwctl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwctl/Cargo.toml b/dwctl/Cargo.toml index ef68c35da..2b410017a 100644 --- a/dwctl/Cargo.toml +++ b/dwctl/Cargo.toml @@ -123,7 +123,7 @@ sqlx = { version = "0.8", features = [ tokio-test = "0.4" serial_test = "3.0" test-log = { version = "0.2", features = ["trace"] } -criterion = { version = "0.5", features = ["async_tokio"] } +criterion = { version = "0.7", features = ["async_tokio"] } tempfile = "3.0" wiremock = "0.6" serde_urlencoded = "0.7" From 5b8487342da380dc75a2aa05830b1061ca65b866 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:39:24 +0000 Subject: [PATCH 08/19] chore(deps): update dependency typescript-eslint to v8.48.0 (#267) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 345 +++++++----------------------------- 1 file changed, 62 insertions(+), 283 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 6fad5907d..adf8d7e9b 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1444,44 +1444,6 @@ "node": ">=18" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", @@ -3687,17 +3649,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz", - "integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", + "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/type-utils": "8.46.4", - "@typescript-eslint/utils": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/type-utils": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3711,7 +3673,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.46.4", + "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -3727,17 +3689,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz", - "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", + "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "engines": { @@ -3753,14 +3715,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz", - "integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", + "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.4", - "@typescript-eslint/types": "^8.46.4", + "@typescript-eslint/tsconfig-utils": "^8.48.0", + "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "engines": { @@ -3775,14 +3737,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz", - "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", + "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4" + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3793,9 +3755,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz", - "integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", + "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", "dev": true, "license": "MIT", "engines": { @@ -3810,15 +3772,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz", - "integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", + "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/utils": "8.46.4", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3835,9 +3797,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz", - "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", "dev": true, "license": "MIT", "engines": { @@ -3849,21 +3811,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz", - "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", + "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.4", - "@typescript-eslint/tsconfig-utils": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/visitor-keys": "8.46.4", + "@typescript-eslint/project-service": "8.48.0", + "@typescript-eslint/tsconfig-utils": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -3917,16 +3878,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz", - "integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", + "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.4", - "@typescript-eslint/types": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4" + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3941,13 +3902,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz", - "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", + "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.4", + "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4507,19 +4468,6 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", @@ -5903,36 +5851,6 @@ "node": ">=6.0.0" } }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -5947,16 +5865,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fault": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", @@ -6000,19 +5908,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -7013,16 +6908,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-number-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", @@ -8177,16 +8062,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -8750,33 +8625,6 @@ ], "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -9494,27 +9342,6 @@ "node": ">=6" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", @@ -10001,17 +9828,6 @@ "dev": true, "license": "MIT" }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { "name": "@rollup/wasm-node", "version": "4.53.2", @@ -10071,30 +9887,6 @@ "dev": true, "license": "MIT" }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -10929,19 +10721,6 @@ "dev": true, "license": "MIT" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -11130,16 +10909,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.46.4", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz", - "integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz", + "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.46.4", - "@typescript-eslint/parser": "8.46.4", - "@typescript-eslint/typescript-estree": "8.46.4", - "@typescript-eslint/utils": "8.46.4" + "@typescript-eslint/eslint-plugin": "8.48.0", + "@typescript-eslint/parser": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" From 0883c9d892cf5ea0c098fdb9a3b295d4f59e97cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:39:40 +0000 Subject: [PATCH 09/19] chore(deps): update tanstack-query monorepo to v5.90.11 (#265) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index adf8d7e9b..f57af190a 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -3224,9 +3224,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.7.tgz", - "integrity": "sha512-6PN65csiuTNfBMXqQUxQhCNdtm1rV+9kC9YwWAIKcaxAauq3Wu7p18j3gQY3YIBJU70jT/wzCCZ2uqto/vQgiQ==", + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz", + "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==", "license": "MIT", "funding": { "type": "github", @@ -3234,9 +3234,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz", - "integrity": "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==", + "version": "5.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.91.1.tgz", + "integrity": "sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg==", "license": "MIT", "funding": { "type": "github", @@ -3244,13 +3244,13 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.7.tgz", - "integrity": "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ==", + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz", + "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==", "license": "MIT", "peer": true, "dependencies": { - "@tanstack/query-core": "5.90.7" + "@tanstack/query-core": "5.90.11" }, "funding": { "type": "github", @@ -3261,19 +3261,19 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.90.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.90.2.tgz", - "integrity": "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==", + "version": "5.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.91.1.tgz", + "integrity": "sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ==", "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.90.1" + "@tanstack/query-devtools": "5.91.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.90.2", + "@tanstack/react-query": "^5.90.10", "react": "^18 || ^19" } }, From 0b9b8f662bbc2c254a722ff76c2b4edf18292017 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:39:49 +0000 Subject: [PATCH 10/19] chore(deps): update dependency @playwright/test to v1.57.0 (#266) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index f57af190a..8dea3e893 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1481,13 +1481,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", - "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.1" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -9182,13 +9182,13 @@ } }, "node_modules/playwright": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", - "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.1" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -9201,9 +9201,9 @@ } }, "node_modules/playwright-core": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", - "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true, "license": "Apache-2.0", "bin": { From 14aa878fd90649637fbd34af8b2ea1396c8405a2 Mon Sep 17 00:00:00 2001 From: Josh C <32071009+JoshC8C7@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:41:58 +0000 Subject: [PATCH 11/19] Remove fusillade package from release-please config (#261) Removed fusillade package configuration from release-please. --- release-please-config.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index 1748749ce..be2360f88 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,16 +1,5 @@ { "packages": { - "fusillade": { - "release-type": "rust", - "include-component-in-tag": true, - "extra-files": [ - { - "type": "toml", - "path": "dwctl/Cargo.toml", - "jsonpath": "$.dependencies.fusillade.version" - } - ] - }, "dwctl": { "release-type": "rust" } From a5d58dc872779a91dd4ce98c706afb88b98c5a14 Mon Sep 17 00:00:00 2001 From: Fergus Date: Fri, 28 Nov 2025 07:43:05 +0000 Subject: [PATCH 12/19] fix: status page was non-navigable, because of a bug with query parameters (#258) --- .../features/models/Models/Models.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/dashboard/src/components/features/models/Models/Models.tsx b/dashboard/src/components/features/models/Models/Models.tsx index 25f69ed19..a5f9f68a3 100644 --- a/dashboard/src/components/features/models/Models/Models.tsx +++ b/dashboard/src/components/features/models/Models/Models.tsx @@ -48,11 +48,23 @@ const Models: React.FC = () => { // sync search query to URL params useEffect(() => { - const params = new URLSearchParams(); - if (searchQuery) params.set("search", searchQuery); - if (currentPage) params.set("page", String(currentPage)); - - setSearchParams(params, { replace: true }); + setSearchParams( + (prev) => { + const params = new URLSearchParams(prev); + if (searchQuery) { + params.set("search", searchQuery); + } else { + params.delete("search"); + } + if (currentPage > 1) { + params.set("page", String(currentPage)); + } else { + params.delete("page"); + } + return params; + }, + { replace: true }, + ); }, [searchQuery, currentPage, setSearchParams]); // reset pagination when search query changes From 491bd31b4f0701a813666bba3552d2b8b73b8b10 Mon Sep 17 00:00:00 2001 From: Seb Ringrose Date: Fri, 28 Nov 2025 09:42:00 +0000 Subject: [PATCH 13/19] feat: optimized batch creation UI (#254) * fix: enter submit create batch modal * fix: page params in batches * feat: new batch upload flow * fix: tests * fix: tests --- dashboard/src/App.tsx | 23 +- .../features/batches/BatchInfo/BatchInfo.tsx | 535 ++++++++++++++++++ .../features/batches/BatchInfo/index.ts | 1 + .../Batches/Batches.pagination.test.tsx | 15 + .../features/batches/Batches/Batches.test.tsx | 108 +++- .../features/batches/Batches/Batches.tsx | 363 ++++++------ .../features/batches/BatchesTable/columns.tsx | 5 +- .../features/batches/FilesTable/columns.tsx | 3 +- .../CreateBatchModal.test.tsx | 489 ++++++++++++++++ .../CreateBatchModal/CreateBatchModal.tsx | 302 +++++++++- dashboard/src/components/ui/data-table.tsx | 13 +- 11 files changed, 1628 insertions(+), 229 deletions(-) create mode 100644 dashboard/src/components/features/batches/BatchInfo/BatchInfo.tsx create mode 100644 dashboard/src/components/features/batches/BatchInfo/index.ts create mode 100644 dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.test.tsx diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 684e6817a..5749cdf29 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -66,6 +66,11 @@ const Batches = lazy(() => default: m.Batches, })), ); +const BatchInfo = lazy(() => + import("./components/features/batches/BatchInfo").then((m) => ({ + default: m.BatchInfo, + })), +); const FileRequests = lazy(() => import("./components/features/batches/FileRequests").then((m) => ({ default: m.FileRequests, @@ -121,7 +126,11 @@ const queryClient = new QueryClient({ mutations: { onError: (error) => { // Handle 401s globally for mutations - if (error instanceof Error && "status" in error && error.status === 401) { + if ( + error instanceof Error && + "status" in error && + error.status === 401 + ) { // Clear all queries and redirect to login queryClient.clear(); window.location.href = "/login"; @@ -279,6 +288,18 @@ function AppRoutes() { } /> + + + }> + + + + + } + /> { + const { batchId } = useParams<{ batchId: string }>(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + + const fromUrl = searchParams.get("from"); + + const { data: batch, isLoading, error } = useBatch(batchId!); + + if (isLoading) { + return ( +
+
+
+

+ Loading batch details... +

+
+
+ ); + } + + if (error) { + return ( +
+
+
+ +
+

+ Error: {error instanceof Error ? error.message : "Unknown error"} +

+ +
+
+ ); + } + + if (!batch) { + return ( +
+
+

Batch not found

+ +
+
+ ); + } + + const getStatusBadge = (status: BatchStatus) => { + const statusConfig: Record< + BatchStatus, + { + label: string; + variant: "default" | "destructive" | "outline" | "secondary"; + icon: React.ReactNode; + } + > = { + validating: { + label: "Validating", + variant: "secondary", + icon: , + }, + in_progress: { + label: "In Progress", + variant: "default", + icon: , + }, + finalizing: { + label: "Finalizing", + variant: "secondary", + icon: , + }, + completed: { + label: "Completed", + variant: "outline", + icon: , + }, + failed: { + label: "Failed", + variant: "destructive", + icon: , + }, + expired: { + label: "Expired", + variant: "outline", + icon: , + }, + cancelling: { + label: "Cancelling", + variant: "secondary", + icon: , + }, + cancelled: { + label: "Cancelled", + variant: "outline", + icon: , + }, + }; + + const config = statusConfig[status]; + return ( + + {config.icon} + {config.label} + + ); + }; + + const formatTimestamp = (timestamp: number | null | undefined) => { + if (!timestamp) return "N/A"; + return new Date(timestamp * 1000).toLocaleString(); + }; + + const formatDuration = ( + startTimestamp: number | null | undefined, + endTimestamp: number | null | undefined, + ) => { + if (!startTimestamp || !endTimestamp) return "N/A"; + const durationMs = (endTimestamp - startTimestamp) * 1000; + const seconds = Math.floor(durationMs / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + + if (hours > 0) { + return `${hours}h ${minutes % 60}m`; + } else if (minutes > 0) { + return `${minutes}m ${seconds % 60}s`; + } else { + return `${seconds}s`; + } + }; + + const progress = + batch.request_counts.total > 0 + ? Math.round( + (batch.request_counts.completed / batch.request_counts.total) * 100, + ) + : 0; + + const description = batch.metadata?.batch_description; + + return ( +
+ {/* Header */} +
+
+ +
+
+
+

+ Batch Details +

+

+ {batch.id} +

+
+
+ {getStatusBadge(batch.status)} +
+
+
+
+
+ +
+ {/* Main Content */} +
+ {/* Progress Card */} + {batch.status !== "failed" && + batch.status !== "cancelled" && + batch.status !== "expired" && ( + + + Progress + + +
+ {/* Progress Bar */} +
+
+ Overall Progress + {progress}% +
+
+
+
+
+ + {/* Request Counts */} +
+
+

+ {batch.request_counts.total} +

+

+ Total Requests +

+
+
+

+ {batch.request_counts.completed} +

+

Completed

+
+
+

+ {batch.request_counts.failed} +

+

Failed

+
+
+
+
+
+ )} + + {/* Batch Details */} + + + Batch Information + + +
+
+
+

Endpoint

+

+ {batch.endpoint} +

+
+
+

+ Completion Window +

+

{batch.completion_window}

+
+
+ + {description && ( +
+

Description

+

{description}

+
+ )} + + {/* Files */} +
+

+ Associated Files +

+
+
+ +
+

+ Input File +

+

+ {batch.input_file_id} +

+
+ +
+ + {batch.output_file_id && ( +
+ +
+

+ Output File +

+

+ {batch.output_file_id} +

+
+ +
+ )} + + {batch.error_file_id && ( +
+ +
+

+ Error File +

+

+ {batch.error_file_id} +

+
+ +
+ )} +
+
+ + {/* Errors */} + {batch.errors && batch.errors.data.length > 0 && ( +
+

+ Errors +

+
+ {batch.errors.data.map((error, index) => ( +
+
+ +
+

+ {error.code} +

+

+ {error.message} +

+ {error.line && ( +

+ Line {error.line} +

+ )} +
+
+
+ ))} +
+
+ )} +
+
+
+
+ + {/* Sidebar */} +
+ {/* Timeline Card */} + + + Timeline + + +
+
+

Created

+

+ {formatTimestamp(batch.created_at)} +

+
+ + {batch.in_progress_at && ( +
+

Started

+

+ {formatTimestamp(batch.in_progress_at)} +

+
+ )} + + {batch.finalizing_at && ( +
+

Finalizing

+

+ {formatTimestamp(batch.finalizing_at)} +

+
+ )} + + {batch.completed_at && ( +
+

Completed

+

+ {formatTimestamp(batch.completed_at)} +

+
+ )} + + {batch.failed_at && ( +
+

Failed

+

+ {formatTimestamp(batch.failed_at)} +

+
+ )} + + {batch.cancelled_at && ( +
+

Cancelled

+

+ {formatTimestamp(batch.cancelled_at)} +

+
+ )} + + {batch.expired_at && ( +
+

Expired

+

+ {formatTimestamp(batch.expired_at)} +

+
+ )} + + {batch.expires_at && !batch.expired_at && ( +
+

Expires

+

+ {formatTimestamp(batch.expires_at)} +

+
+ )} + + {/* Duration */} + {batch.in_progress_at && batch.completed_at && ( +
+

Duration

+

+ {formatDuration(batch.in_progress_at, batch.completed_at)} +

+
+ )} +
+
+
+ + {/* Metadata Card */} + {batch.metadata && Object.keys(batch.metadata).length > 0 && ( + + + Metadata + + +
+ {Object.entries(batch.metadata).map(([key, value]) => ( +
+

{key}

+

+ {value} +

+
+ ))} +
+
+
+ )} +
+
+
+ ); +}; + +export default BatchInfo; diff --git a/dashboard/src/components/features/batches/BatchInfo/index.ts b/dashboard/src/components/features/batches/BatchInfo/index.ts new file mode 100644 index 000000000..c1d6f3515 --- /dev/null +++ b/dashboard/src/components/features/batches/BatchInfo/index.ts @@ -0,0 +1 @@ +export { default as BatchInfo } from "./BatchInfo"; diff --git a/dashboard/src/components/features/batches/Batches/Batches.pagination.test.tsx b/dashboard/src/components/features/batches/Batches/Batches.pagination.test.tsx index bc4b16f3f..825a508b2 100644 --- a/dashboard/src/components/features/batches/Batches/Batches.pagination.test.tsx +++ b/dashboard/src/components/features/batches/Batches/Batches.pagination.test.tsx @@ -178,6 +178,9 @@ describe("Batches - Pagination", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + // Wait for initial render await waitFor(() => { expect( @@ -278,6 +281,9 @@ describe("Batches - Pagination", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + // Wait for page 1 await waitFor(() => { expect( @@ -389,6 +395,9 @@ describe("Batches - Pagination", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + await waitFor(() => { expect( within(container).getByText("file_page1_0.jsonl"), @@ -523,6 +532,9 @@ describe("Batches - Pagination", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + await waitFor(() => { expect( within(container).getByText("file_page1_0.jsonl"), @@ -636,6 +648,9 @@ describe("Batches - Pagination", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + await waitFor(() => { expect( within(container).getByText("file_page1_0.jsonl"), diff --git a/dashboard/src/components/features/batches/Batches/Batches.test.tsx b/dashboard/src/components/features/batches/Batches/Batches.test.tsx index 833bcda72..804e14939 100644 --- a/dashboard/src/components/features/batches/Batches/Batches.test.tsx +++ b/dashboard/src/components/features/batches/Batches/Batches.test.tsx @@ -226,13 +226,14 @@ describe("Batches", () => { ).toBeInTheDocument(); }); - it("should render upload file button", () => { + it("should render create batch button on batches tab", () => { const { container } = render(, { wrapper: createWrapper(), }); + // On batches tab by default, should show "Create Batch" button expect( - within(container).getByRole("button", { name: /upload file/i }), + within(container).getByRole("button", { name: /create batch/i }), ).toBeInTheDocument(); }); @@ -242,12 +243,45 @@ describe("Batches", () => { }); expect( - within(container).getByRole("tab", { name: /files \(2\)/i }), + within(container).getByRole("tab", { name: /files/i }), ).toBeInTheDocument(); expect( - within(container).getByRole("tab", { name: /batches \(2\)/i }), + within(container).getByRole("tab", { name: /batches/i }), ).toBeInTheDocument(); }); + + it("should fetch both files and batches on initial render", () => { + const useFilesSpy = vi.mocked(hooks.useFiles); + const useBatchesSpy = vi.mocked(hooks.useBatches); + + render(, { + wrapper: createWrapper(), + }); + + // Verify both queries were called (not disabled) + expect(useFilesSpy).toHaveBeenCalled(); + expect(useBatchesSpy).toHaveBeenCalled(); + }); + + it("should show correct tab when starting on files tab", () => { + const { container } = render(, { + wrapper: createWrapper(), + }); + + const filesTab = within(container).getByRole("tab", { + name: /files/i, + }); + const batchesTab = within(container).getByRole("tab", { + name: /batches/i, + }); + + expect(filesTab).toBeInTheDocument(); + expect(batchesTab).toBeInTheDocument(); + + // Verify batches tab is active by default + expect(batchesTab).toHaveAttribute("data-state", "active"); + expect(filesTab).toHaveAttribute("data-state", "inactive"); + }); }); describe("Loading State", () => { @@ -303,7 +337,8 @@ describe("Batches", () => { }); describe("Empty States", () => { - it("should show empty state when no files exist", () => { + it("should show empty state when no files exist", async () => { + const user = userEvent.setup(); vi.mocked(hooks.useFiles).mockReturnValue({ data: { data: [] }, isLoading: false, @@ -315,6 +350,9 @@ describe("Batches", () => { wrapper: createWrapper(), }); + // Switch to files tab + await user.click(within(container).getByRole("tab", { name: /files/i })); + expect( within(container).getByText("No files uploaded"), ).toBeInTheDocument(); @@ -362,11 +400,15 @@ describe("Batches", () => { }); describe("Files Tab", () => { - it("should display files in the table", () => { + it("should display files in the table", async () => { + const user = userEvent.setup(); const { container } = render(, { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + expect( within(container).getByText("test_file.jsonl"), ).toBeInTheDocument(); @@ -383,6 +425,9 @@ describe("Batches", () => { { wrapper: createWrapper() }, ); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + await user.click( within(container).getByRole("button", { name: /upload file/i }), ); @@ -396,6 +441,9 @@ describe("Batches", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + const searchInput = within(container).getByPlaceholderText(/search files/i); await user.type(searchInput, "test"); @@ -409,6 +457,9 @@ describe("Batches", () => { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + const searchInput = within(container).getByPlaceholderText(/search files/i); await user.type(searchInput, "test_file"); @@ -465,26 +516,25 @@ describe("Batches", () => { }); describe("Tab Switching", () => { - it("should maintain search when switching tabs", async () => { + it("should have independent search when switching tabs", async () => { const user = userEvent.setup(); const { container } = render(, { wrapper: createWrapper(), }); - // Search in files tab - const fileSearch = - within(container).getByPlaceholderText(/search files/i); - await user.type(fileSearch, "test"); - - // Switch to batches - await user.click( - within(container).getByRole("tab", { name: /batches/i }), - ); - - // Search should be cleared or independent + // Start on batches tab - search batches const batchSearch = within(container).getByPlaceholderText(/search batches/i); - expect(batchSearch).toHaveValue(""); + await user.type(batchSearch, "batch-1"); + expect(batchSearch).toHaveValue("batch-1"); + + // Switch to files tab + await user.click(within(container).getByRole("tab", { name: /files/i })); + + // Files search should be empty (independent from batches search) + const fileSearch = + within(container).getByPlaceholderText(/search files/i); + expect(fileSearch).toHaveValue(""); }); }); @@ -497,6 +547,9 @@ describe("Batches", () => { { wrapper: createWrapper() }, ); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + await user.click( within(container).getByRole("button", { name: /upload file/i }), ); @@ -511,22 +564,30 @@ describe("Batches", () => { }); describe("File Size Display", () => { - it("should display file sizes correctly", () => { + it("should display file sizes correctly", async () => { + const user = userEvent.setup(); const { container } = render(, { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + // File sizes should be formatted (e.g., "142.19 KB", "87.11 KB") within(container).getByText("test_file.jsonl").closest("table"); }); }); describe("Date Formatting", () => { - it("should display created dates for files", () => { + it("should display created dates for files", async () => { + const user = userEvent.setup(); const { container } = render(, { wrapper: createWrapper(), }); + // Switch to files tab first + await user.click(within(container).getByRole("tab", { name: /files/i })); + // Dates should be formatted and displayed within(container).getByText("test_file.jsonl").closest("table"); }); @@ -544,7 +605,7 @@ describe("Batches", () => { }); expect(filesTab).toHaveAttribute("aria-selected"); - expect(batchesTab).not.toHaveAttribute("aria-selected", "true"); + expect(batchesTab).toHaveAttribute("aria-selected", "true"); }); it("should have accessible action buttons", () => { @@ -552,8 +613,9 @@ describe("Batches", () => { wrapper: createWrapper(), }); + // On batches tab by default, should have "Create Batch" button expect( - within(container).getByRole("button", { name: /upload file/i }), + within(container).getByRole("button", { name: /create batch/i }), ).toBeEnabled(); }); }); diff --git a/dashboard/src/components/features/batches/Batches/Batches.tsx b/dashboard/src/components/features/batches/Batches/Batches.tsx index d665e957f..fa95646df 100644 --- a/dashboard/src/components/features/batches/Batches/Batches.tsx +++ b/dashboard/src/components/features/batches/Batches/Batches.tsx @@ -94,48 +94,29 @@ export function Batches({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [onBatchCreatedCallback]); - // Read state from URL - const activeTab = (searchParams.get("tab") as "files" | "batches") || "files"; + // Read state from URL - default to batches tab + const activeTab = + (searchParams.get("tab") as "files" | "batches") || "batches"; const batchFileFilter = searchParams.get("fileFilter"); const fileTypeFilter = (searchParams.get("fileType") as "input" | "output" | "error") || "input"; // Pagination state from URL - const filesPage = parseInt(searchParams.get("filesPage") || "0", 10); + const filesPage = parseInt(searchParams.get("filesPage") || "1", 10); const filesPageSize = parseInt(searchParams.get("filesPageSize") || "10", 10); - const [filesAfterCursor, setFilesAfterCursor] = useState( - undefined, - ); + const filesAfterCursor = searchParams.get("filesAfter") || undefined; - const batchesPage = parseInt(searchParams.get("batchesPage") || "0", 10); + const batchesPage = parseInt(searchParams.get("batchesPage") || "1", 10); const batchesPageSize = parseInt( searchParams.get("batchesPageSize") || "10", 10, ); - const [batchesAfterCursor, setBatchesAfterCursor] = useState< - string | undefined - >(undefined); + const batchesAfterCursor = searchParams.get("batchesAfter") || undefined; // Cursor history for backwards pagination const filesCursorHistory = React.useRef<(string | undefined)[]>([]); const batchesCursorHistory = React.useRef<(string | undefined)[]>([]); - // Update files pagination in URL - const updateFilesPagination = (newPage: number, newPageSize: number) => { - const params = new URLSearchParams(searchParams); - params.set("filesPage", newPage.toString()); - params.set("filesPageSize", newPageSize.toString()); - setSearchParams(params, { replace: true }); - }; - - // Update batches pagination in URL - const updateBatchesPagination = (newPage: number, newPageSize: number) => { - const params = new URLSearchParams(searchParams); - params.set("batchesPage", newPage.toString()); - params.set("batchesPageSize", newPageSize.toString()); - setSearchParams(params, { replace: true }); - }; - // API queries // Paginated files query for display in Files tab // Map fileType to purpose filter @@ -150,14 +131,14 @@ export function Batches({ purpose: filePurpose, limit: filesPageSize + 1, // Fetch one extra to detect if there are more after: filesAfterCursor, - enabled: activeTab === "files", // Only fetch when on files tab + // Always fetch to populate tab counts, but refetch interval is lower when not active }); // Paginated batches query const { data: batchesResponse, isLoading: batchesLoading } = useBatches({ limit: batchesPageSize + 1, // Fetch one extra to detect if there are more after: batchesAfterCursor, - enabled: activeTab === "batches", // Only fetch when on batches tab + // Always fetch to populate tab counts, but refetch interval is lower when not active }); // Process batches response - remove extra item used for hasMore detection @@ -320,8 +301,11 @@ export function Batches({ if (lastFile && filesHasMore) { // Save current cursor to history before moving forward filesCursorHistory.current[filesPage] = filesAfterCursor; - setFilesAfterCursor(lastFile.id); - updateFilesPagination(filesPage + 1, filesPageSize); + const params = new URLSearchParams(searchParams); + params.set("filesPage", (filesPage + 1).toString()); + params.set("filesPageSize", filesPageSize.toString()); + params.set("filesAfter", lastFile.id); + setSearchParams(params, { replace: true }); } }; @@ -329,15 +313,25 @@ export function Batches({ if (filesPage > 0) { // Use cursor history to go back one page const previousCursor = filesCursorHistory.current[filesPage - 1]; - setFilesAfterCursor(previousCursor); - updateFilesPagination(filesPage - 1, filesPageSize); + const params = new URLSearchParams(searchParams); + params.set("filesPage", (filesPage - 1).toString()); + params.set("filesPageSize", filesPageSize.toString()); + if (previousCursor) { + params.set("filesAfter", previousCursor); + } else { + params.delete("filesAfter"); + } + setSearchParams(params, { replace: true }); } }; const handleFilesPageSizeChange = (newSize: number) => { - setFilesAfterCursor(undefined); filesCursorHistory.current = []; // Clear history when changing page size - updateFilesPagination(0, newSize); + const params = new URLSearchParams(searchParams); + params.set("filesPage", "1"); + params.set("filesPageSize", newSize.toString()); + params.delete("filesAfter"); + setSearchParams(params, { replace: true }); }; const handleBatchesNextPage = () => { @@ -345,8 +339,11 @@ export function Batches({ if (lastBatch && batchesHasMore) { // Save current cursor to history before moving forward batchesCursorHistory.current[batchesPage] = batchesAfterCursor; - setBatchesAfterCursor(lastBatch.id); - updateBatchesPagination(batchesPage + 1, batchesPageSize); + const params = new URLSearchParams(searchParams); + params.set("batchesPage", (batchesPage + 1).toString()); + params.set("batchesPageSize", batchesPageSize.toString()); + params.set("batchesAfter", lastBatch.id); + setSearchParams(params, { replace: true }); } }; @@ -354,15 +351,25 @@ export function Batches({ if (batchesPage > 0) { // Use cursor history to go back one page const previousCursor = batchesCursorHistory.current[batchesPage - 1]; - setBatchesAfterCursor(previousCursor); - updateBatchesPagination(batchesPage - 1, batchesPageSize); + const params = new URLSearchParams(searchParams); + params.set("batchesPage", (batchesPage - 1).toString()); + params.set("batchesPageSize", batchesPageSize.toString()); + if (previousCursor) { + params.set("batchesAfter", previousCursor); + } else { + params.delete("batchesAfter"); + } + setSearchParams(params, { replace: true }); } }; const handleBatchesPageSizeChange = (newSize: number) => { - setBatchesAfterCursor(undefined); batchesCursorHistory.current = []; // Clear history when changing page size - updateBatchesPagination(0, newSize); + const params = new URLSearchParams(searchParams); + params.set("batchesPage", "1"); + params.set("batchesPageSize", newSize.toString()); + params.delete("batchesAfter"); + setSearchParams(params, { replace: true }); }; // Check if a file's associated batch is still in progress @@ -401,11 +408,17 @@ export function Batches({ isFileInProgress, }); + const handleBatchClick = (batch: Batch) => { + if ((batch as any)._isEmpty) return; + navigate(`/batches/${batch.id}?from=/batches`); + }; + const batchColumns = createBatchColumns({ onCancel: handleCancelBatch, getBatchFiles, onViewFile: handleViewFileRequests, getInputFile, + onRowClick: handleBatchClick, }); // Loading state @@ -458,39 +471,156 @@ export function Batches({ {/* Right: Buttons + Tabs */}
- {/* Action Button */} - + {/* Action Button - changes based on active tab */} + {activeTab === "batches" ? ( + + ) : ( + + )} {/* Tabs Selector */} - - Files ({files.length}) + + Batches - - Batches ({batches.length}) + + Files
{/* Content */} + + {/* Show filter indicator if active */} + {batchFileFilter && ( +
+ + + Showing batches for file:{" "} + + {files.find((f) => f.id === batchFileFilter)?.filename || + batchFileFilter} + + + +
+ )} + {filteredBatches.length === 0 ? ( +
+
+ +
+

+ No batches created +

+

+ Create a batch from an uploaded file to start processing + requests +

+ +
+ ) : ( + <> + + Rows: + + + } + /> + { + batchesCursorHistory.current = []; + const params = new URLSearchParams(searchParams); + params.set("batchesPage", "1"); + params.set("batchesPageSize", batchesPageSize.toString()); + params.delete("batchesAfter"); + setSearchParams(params, { replace: true }); + }} + hasNextPage={batchesHasMore} + hasPrevPage={batchesPage > 1} + currentPageItemCount={filteredBatches.length} + itemName="batches" + /> + + )} +
+ {files.length === 0 ? (
@@ -555,14 +685,14 @@ export function Batches({ params.delete("fileType"); } // Reset pagination - params.set("filesPage", "0"); + params.set("filesPage", "1"); params.set( "filesPageSize", filesPageSize.toString(), ); + params.delete("filesAfter"); setSearchParams(params, { replace: false }); - // Reset cursor and history - setFilesAfterCursor(undefined); + // Reset cursor history filesCursorHistory.current = []; }} className={`inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${ @@ -599,125 +729,26 @@ export function Batches({ } /> { - setFilesAfterCursor(undefined); filesCursorHistory.current = []; - updateFilesPagination(0, filesPageSize); + const params = new URLSearchParams(searchParams); + params.set("filesPage", "1"); + params.set("filesPageSize", filesPageSize.toString()); + params.delete("filesAfter"); + setSearchParams(params, { replace: true }); }} hasNextPage={filesHasMore} - hasPrevPage={filesPage > 0} + hasPrevPage={filesPage > 1} currentPageItemCount={files.length} itemName="files" /> )} - - - {/* Show filter indicator if active */} - {batchFileFilter && ( -
- - - Showing batches for file:{" "} - - {files.find((f) => f.id === batchFileFilter)?.filename || - batchFileFilter} - - - -
- )} - {filteredBatches.length === 0 ? ( -
-
- -
-

- No batches created -

-

- Create a batch from an uploaded file to start processing - requests -

- -
- ) : ( - <> - - Rows: - -
- } - /> - { - setBatchesAfterCursor(undefined); - batchesCursorHistory.current = []; - updateBatchesPagination(0, batchesPageSize); - }} - hasNextPage={batchesHasMore} - hasPrevPage={batchesPage > 0} - currentPageItemCount={filteredBatches.length} - itemName="batches" - /> - - )} -
); diff --git a/dashboard/src/components/features/batches/BatchesTable/columns.tsx b/dashboard/src/components/features/batches/BatchesTable/columns.tsx index 37331b8b2..ad181ae69 100644 --- a/dashboard/src/components/features/batches/BatchesTable/columns.tsx +++ b/dashboard/src/components/features/batches/BatchesTable/columns.tsx @@ -25,6 +25,7 @@ interface ColumnActions { getBatchFiles: (batch: Batch) => any[]; onViewFile: (file: any) => void; getInputFile: (batch: Batch) => any | undefined; + onRowClick?: (batch: Batch) => void; } const getStatusIcon = (status: BatchStatus) => { @@ -266,7 +267,7 @@ export const createBatchColumns = ( }} > - + {formatNumber(outputCount)} @@ -291,7 +292,7 @@ export const createBatchColumns = ( }} > - + {formatNumber(errorCount)} diff --git a/dashboard/src/components/features/batches/FilesTable/columns.tsx b/dashboard/src/components/features/batches/FilesTable/columns.tsx index 97b0f7d5d..e25e8deab 100644 --- a/dashboard/src/components/features/batches/FilesTable/columns.tsx +++ b/dashboard/src/components/features/batches/FilesTable/columns.tsx @@ -150,13 +150,14 @@ export const createFileColumns = ( }, { id: "actions", + header: "Actions", cell: ({ row }) => { const file = row.original; const isExpired = file.expires_at && new Date(file.expires_at * 1000) < new Date(); return ( -
+
{!isExpired && file.purpose === "batch" && ( diff --git a/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.test.tsx b/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.test.tsx new file mode 100644 index 000000000..54304ef1d --- /dev/null +++ b/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.test.tsx @@ -0,0 +1,489 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { CreateBatchModal } from "./CreateBatchModal"; +import * as hooks from "../../../api/control-layer/hooks"; + +// Mock the hooks +vi.mock("../../../api/control-layer/hooks", () => ({ + useCreateBatch: vi.fn(), + useUploadFile: vi.fn(), + useFiles: vi.fn(), +})); + +// Mock sonner toast +vi.mock("sonner", () => ({ + toast: { + success: vi.fn(), + error: vi.fn(), + }, +})); + +const mockFile = { + id: "file-123", + object: "file" as const, + bytes: 1024000, + created_at: 1730995200, + expires_at: 1765065600, + filename: "test-batch.jsonl", + purpose: "batch" as const, +}; + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false, gcTime: 0, staleTime: 0 }, + mutations: { retry: false }, + }, + }); + return ({ children }: { children: React.ReactNode }) => ( + {children} + ); +}; + +describe("CreateBatchModal", () => { + beforeEach(() => { + vi.clearAllMocks(); + + // Default mock for useUploadFile + vi.mocked(hooks.useUploadFile).mockReturnValue({ + mutateAsync: vi.fn(), + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + // Default mock for useFiles + vi.mocked(hooks.useFiles).mockReturnValue({ + data: { data: [] }, + isLoading: false, + error: null, + refetch: vi.fn(), + } as any); + }); + + describe("Basic interactions", () => { + it("should close modal when Cancel button is clicked", async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find and click the Cancel button + const cancelButton = screen.getByRole("button", { name: /cancel/i }); + await user.click(cancelButton); + + // Verify onClose was called + expect(onClose).toHaveBeenCalled(); + // Verify mutation was NOT called + expect(mutateAsync).not.toHaveBeenCalled(); + }); + + it("should submit when Create Batch button is clicked", async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const onSuccess = vi.fn(); + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Add a description + const descriptionInput = screen.getByPlaceholderText( + /daily evaluation batch/i, + ); + await user.type(descriptionInput, "Test batch"); + + // Find and click the Create Batch button + const createButton = screen.getByRole("button", { + name: /create batch/i, + }); + await user.click(createButton); + + // Verify the mutation was called + await waitFor(() => { + expect(mutateAsync).toHaveBeenCalledWith({ + input_file_id: "file-123", + endpoint: "/v1/chat/completions", + completion_window: "24h", + metadata: { + batch_description: "Test batch", + }, + }); + }); + + // Verify callbacks were called + await waitFor(() => { + expect(onSuccess).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + }); + }); + + it("should disable Create Batch button when no file is selected", async () => { + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find the Create Batch button + const createButton = screen.getByRole("button", { + name: /create batch/i, + }); + + // Verify it's disabled + expect(createButton).toBeDisabled(); + }); + + it("should disable buttons when mutation is pending", async () => { + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: true, // Mutation in progress + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "pending", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: false, + isPaused: false, + submittedAt: Date.now(), + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find buttons + const cancelButton = screen.getByRole("button", { name: /cancel/i }); + const createButton = screen.getByRole("button", { name: /creating/i }); + + // Verify they're disabled + expect(cancelButton).toBeDisabled(); + expect(createButton).toBeDisabled(); + }); + }); + + describe("Enter key submission", () => { + it("should submit the form when Enter is pressed in description field", async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const onSuccess = vi.fn(); + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find and focus the description input - use screen since Dialog renders in a portal + const descriptionInput = screen.getByPlaceholderText( + /daily evaluation batch/i, + ); + await user.click(descriptionInput); + await user.type(descriptionInput, "Test batch description"); + + // Press Enter + await user.keyboard("{Enter}"); + + // Verify the mutation was called + await waitFor(() => { + expect(mutateAsync).toHaveBeenCalledWith({ + input_file_id: "file-123", + endpoint: "/v1/chat/completions", + completion_window: "24h", + metadata: { + batch_description: "Test batch description", + }, + }); + }); + + // Verify callbacks were called + await waitFor(() => { + expect(onSuccess).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + }); + }); + + it("should not submit when Enter is pressed if no file is selected", async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find and focus the description input - use screen since Dialog renders in a portal + const descriptionInput = screen.getByPlaceholderText( + /daily evaluation batch/i, + ); + await user.click(descriptionInput); + await user.type(descriptionInput, "Test description"); + + // Press Enter + await user.keyboard("{Enter}"); + + // Verify the mutation was NOT called + expect(mutateAsync).not.toHaveBeenCalled(); + }); + + it("should not submit when Enter is pressed if mutation is pending", async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: true, // Mutation in progress + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "pending", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: false, + isPaused: false, + submittedAt: Date.now(), + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find and focus the description input - use screen since Dialog renders in a portal + const descriptionInput = screen.getByPlaceholderText( + /daily evaluation batch/i, + ); + await user.click(descriptionInput); + await user.type(descriptionInput, "Test description"); + + // Press Enter + await user.keyboard("{Enter}"); + + // Verify the mutation was NOT called again + expect(mutateAsync).not.toHaveBeenCalled(); + }); + + it("should submit with empty description when Enter is pressed", async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const onSuccess = vi.fn(); + const mutateAsync = vi.fn().mockResolvedValue({}); + + vi.mocked(hooks.useCreateBatch).mockReturnValue({ + mutateAsync, + isPending: false, + isError: false, + error: null, + isSuccess: false, + data: undefined, + mutate: vi.fn(), + reset: vi.fn(), + status: "idle", + context: undefined, + failureCount: 0, + failureReason: null, + isIdle: true, + isPaused: false, + submittedAt: 0, + variables: undefined, + } as any); + + render( + , + { wrapper: createWrapper() }, + ); + + // Find and focus the description input (don't type anything) - use screen since Dialog renders in a portal + const descriptionInput = screen.getByPlaceholderText( + /daily evaluation batch/i, + ); + await user.click(descriptionInput); + + // Press Enter without typing + await user.keyboard("{Enter}"); + + // Verify the mutation was called without metadata + await waitFor(() => { + expect(mutateAsync).toHaveBeenCalledWith({ + input_file_id: "file-123", + endpoint: "/v1/chat/completions", + completion_window: "24h", + metadata: undefined, + }); + }); + + // Verify callbacks were called + await waitFor(() => { + expect(onSuccess).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.tsx b/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.tsx index 65cd6f9ab..9e488ee6f 100644 --- a/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.tsx +++ b/dashboard/src/components/modals/CreateBatchModal/CreateBatchModal.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { Play, AlertCircle } from "lucide-react"; +import { useState, useEffect } from "react"; +import { Play, AlertCircle, X, Upload } from "lucide-react"; import { Dialog, DialogContent, @@ -11,7 +11,12 @@ import { import { Button } from "../../ui/button"; import { Label } from "../../ui/label"; import { Input } from "../../ui/input"; -import { useCreateBatch } from "../../../api/control-layer/hooks"; +import { Combobox } from "../../ui/combobox"; +import { + useCreateBatch, + useFiles, + useUploadFile, +} from "../../../api/control-layer/hooks"; import { toast } from "sonner"; import type { FileObject } from "../../features/batches/types"; import { AlertBox } from "@/components/ui/alert-box"; @@ -29,15 +34,114 @@ export function CreateBatchModal({ onSuccess, preselectedFile, }: CreateBatchModalProps) { + const [selectedFileId, setSelectedFileId] = useState( + preselectedFile?.id || null, + ); + const [fileToUpload, setFileToUpload] = useState(null); + const [expirationSeconds, setExpirationSeconds] = useState(2592000); // 30 days default const [endpoint, setEndpoint] = useState("/v1/chat/completions"); const [description, setDescription] = useState(""); const [error, setError] = useState(null); + const [dragActive, setDragActive] = useState(false); + const [isUploading, setIsUploading] = useState(false); const createBatchMutation = useCreateBatch(); + const uploadMutation = useUploadFile(); + + // Fetch available files for combobox (only input files with purpose "batch") + const { data: filesResponse } = useFiles({ + purpose: "batch", + limit: 1000, // Fetch plenty for the dropdown + }); + + const availableFiles = filesResponse?.data || []; + + // Update selected file when preselected file changes + useEffect(() => { + if (preselectedFile) { + setSelectedFileId(preselectedFile.id); + setFileToUpload(null); // Clear any file to upload + } + }, [preselectedFile]); + + const handleDrag = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") { + setDragActive(true); + } else if (e.type === "dragleave") { + setDragActive(false); + } + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + const droppedFile = e.dataTransfer.files[0]; + if (droppedFile.name.endsWith(".jsonl")) { + setFileToUpload(droppedFile); + setSelectedFileId(null); // Clear combobox selection + setError(null); + } else { + setError("Please upload a .jsonl file"); + } + } + }; + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + const file = e.target.files[0]; + if (file.name.endsWith(".jsonl")) { + setFileToUpload(file); + setSelectedFileId(null); // Clear combobox selection + setError(null); + } else { + setError("Please upload a .jsonl file"); + } + } + }; + + const handleRemoveFile = () => { + setFileToUpload(null); + setSelectedFileId(null); + }; const handleSubmit = async () => { - if (!preselectedFile) { - setError("No file selected"); + let finalFileId = selectedFileId; + + // If a file needs to be uploaded, upload it first + if (fileToUpload) { + setIsUploading(true); + try { + const uploadedFile = await uploadMutation.mutateAsync({ + file: fileToUpload, + purpose: "batch", + expires_after: { + anchor: "created_at", + seconds: expirationSeconds, + }, + }); + finalFileId = uploadedFile.id; + toast.success(`File "${fileToUpload.name}" uploaded successfully`); + } catch (error) { + console.error("Failed to upload file:", error); + setError( + error instanceof Error + ? error.message + : "Failed to upload file. Please try again.", + ); + setIsUploading(false); + return; + } finally { + setIsUploading(false); + } + } + + if (!finalFileId) { + setError("Please select or upload a file"); return; } @@ -48,18 +152,25 @@ export function CreateBatchModal({ } await createBatchMutation.mutateAsync({ - input_file_id: preselectedFile.id, + input_file_id: finalFileId, endpoint, completion_window: "24h", metadata: Object.keys(metadata).length > 0 ? metadata : undefined, }); - toast.success( - `Batch created successfully from "${preselectedFile.filename}"`, - ); + const fileName = + fileToUpload?.name || + availableFiles.find((f) => f.id === finalFileId)?.filename || + "file"; + toast.success(`Batch created successfully from "${fileName}"`); + // Reset form + setSelectedFileId(null); + setFileToUpload(null); setEndpoint("/v1/chat/completions"); setDescription(""); + setExpirationSeconds(2592000); + setError(null); onSuccess?.(); onClose(); } catch (error) { @@ -69,18 +180,33 @@ export function CreateBatchModal({ }; const handleClose = () => { + setSelectedFileId(preselectedFile?.id || null); + setFileToUpload(null); setEndpoint("/v1/chat/completions"); setDescription(""); + setExpirationSeconds(2592000); + setError(null); onClose(); }; + const selectedFile = selectedFileId + ? availableFiles.find((f) => f.id === selectedFileId) + : null; + + const fileOptions = availableFiles.map((file) => ({ + value: file.id, + label: file.filename, + })); + + const isPending = createBatchMutation.isPending || isUploading; + return ( Create New Batch - Enter a description for the batch. + Select an existing file or upload a new one to create a batch. @@ -89,23 +215,124 @@ export function CreateBatchModal({
- {/* File Info */} - {preselectedFile && ( -
- -
-

- {preselectedFile.filename} -

-
- - Size: {(preselectedFile.bytes / 1024).toFixed(1)} KB - - ID: {preselectedFile.id} + {/* File Selection/Upload */} +
+ + + {/* Show selected file or file to upload */} + {selectedFile || fileToUpload ? ( +
+
+
+

+ {fileToUpload?.name || selectedFile?.filename} +

+
+ {fileToUpload ? ( + <> + + Size: {(fileToUpload.size / 1024).toFixed(1)} KB + + Ready to upload + + ) : ( + selectedFile && ( + <> + + Size: {(selectedFile.bytes / 1024).toFixed(1)} KB + + ID: {selectedFile.id} + + ) + )} +
+
+
-
- )} + ) : ( + <> + {/* Combobox for selecting existing file */} + {availableFiles.length > 0 && ( +
+ { + setSelectedFileId(value); + setFileToUpload(null); // Clear file to upload + setError(null); + }} + placeholder="Select an existing file..." + searchPlaceholder="Search files..." + emptyMessage="No files found." + className="w-full" + /> +

+ Choose from your uploaded batch files +

+
+ )} + + {/* Separator */} + {availableFiles.length > 0 && ( +
+
+ +
+
+ + Or + +
+
+ )} + + {/* Drop zone for new file */} +
+ + +
+ +
+

+ Drop a .jsonl file here +

+

+ or click to browse +

+
+
+
+ + )} +
{/* Description (Optional) */}
@@ -117,7 +344,18 @@ export function CreateBatchModal({ placeholder="e.g., Daily evaluation batch" value={description} onChange={(e) => setDescription(e.target.value)} + onKeyDown={(e) => { + if ( + e.key === "Enter" && + !isPending && + (selectedFileId || fileToUpload) + ) { + e.preventDefault(); + handleSubmit(); + } + }} maxLength={512} + disabled={isPending} />

Add a description to help identify this batch later @@ -131,8 +369,10 @@ export function CreateBatchModal({

Batch Processing

- The batch will process all requests in the selected file. You - can track progress and download results once completed. + {fileToUpload + ? "The file will be uploaded and the batch will process all requests. " + : "The batch will process all requests in the selected file. "} + You can track progress and download results once completed.

@@ -144,7 +384,7 @@ export function CreateBatchModal({ type="button" variant="outline" onClick={handleClose} - disabled={createBatchMutation.isPending} + disabled={isPending} > Cancel @@ -152,13 +392,13 @@ export function CreateBatchModal({ type="button" variant="outline" onClick={handleSubmit} - disabled={!preselectedFile || createBatchMutation.isPending} + disabled={(!selectedFileId && !fileToUpload) || isPending} className="group" > - {createBatchMutation.isPending ? ( + {isPending ? ( <>
- Creating... + {isUploading ? "Uploading..." : "Creating..."} ) : ( <> diff --git a/dashboard/src/components/ui/data-table.tsx b/dashboard/src/components/ui/data-table.tsx index 06d6829bb..7e83ec04b 100644 --- a/dashboard/src/components/ui/data-table.tsx +++ b/dashboard/src/components/ui/data-table.tsx @@ -47,6 +47,7 @@ interface DataTableProps { actionBar?: React.ReactNode; headerActions?: React.ReactNode; initialColumnVisibility?: VisibilityState; + onRowClick?: (row: TData) => void; } export function DataTable({ @@ -63,6 +64,7 @@ export function DataTable({ actionBar, headerActions, initialColumnVisibility = {}, + onRowClick, }: DataTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( @@ -184,7 +186,7 @@ export function DataTable({ )}
-
+
{table.getHeaderGroups().map((headerGroup) => ( @@ -245,10 +247,11 @@ export function DataTable({ onRowClick?.(row.original)} > - {row.getVisibleCells().map((cell) => ( + {row.getVisibleCells().map((cell, index, cells) => ( ({ ? "pl-6 w-[50px]" : cell.column.getIndex() === 0 ? "pl-6" - : cell.column.id === "actions" - ? "pr-6" + : index === cells.length - 1 + ? "pr-0" : "" } > From 1525e60f633337ebe2308e39df054e342cbe1f52 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:50:03 +0000 Subject: [PATCH 14/19] fix(deps): update rust crate prometheus to 0.14 (#273) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dwctl/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dwctl/Cargo.toml b/dwctl/Cargo.toml index 2b410017a..265e41339 100644 --- a/dwctl/Cargo.toml +++ b/dwctl/Cargo.toml @@ -89,7 +89,7 @@ rust_decimal = { version = "1.38.0", features = ["serde"] } bon = "3.3" once_cell = "1.20" # Prometheus for GenAI metrics (via axum-prometheus) -prometheus = "0.13" +prometheus = "0.14" # Embedded database support postgresql_embedded = { version = "0.20", optional = true, features = [ "bundled", From 6827d5307367ada397484d6b679d4361b2184cb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:50:19 +0000 Subject: [PATCH 15/19] chore(deps): update actions/checkout action to v6 (#276) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9d26bb922..f6dfbbc8b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v4 @@ -115,7 +115,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -192,7 +192,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -258,7 +258,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 @@ -331,7 +331,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install just uses: extractions/setup-just@v2 @@ -389,7 +389,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v4 From c36ea9a143c9bbfbbe286bb07c89a77e43d2ef6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:00:43 +0000 Subject: [PATCH 16/19] chore(deps): update actions/setup-node action to v6 (#277) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f6dfbbc8b..069d0d927 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "20" cache: "npm" @@ -337,7 +337,7 @@ jobs: uses: extractions/setup-just@v2 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "20" cache: "npm" @@ -392,7 +392,7 @@ jobs: uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "20" cache: "npm" From 67253bcde8de89be49a2381170d330f51aebc01c Mon Sep 17 00:00:00 2001 From: pjb157 <84070455+pjb157@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:02:40 +0000 Subject: [PATCH 17/19] chore(main): release 0.11.0 (#257) --- .release-please-manifest.json | 2 +- Cargo.lock | 2 +- dwctl/CHANGELOG.md | 13 +++++++++++++ dwctl/Cargo.lock | 2 +- dwctl/Cargo.toml | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 924e947c2..4fb44c211 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{"dwctl":"0.10.1","fusillade":"0.3.0"} +{"dwctl":"0.11.0","fusillade":"0.3.0"} diff --git a/Cargo.lock b/Cargo.lock index dca37ae83..bfdbf23e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1259,7 +1259,7 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dwctl" -version = "0.10.1" +version = "0.11.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/dwctl/CHANGELOG.md b/dwctl/CHANGELOG.md index fda344dd8..832337f26 100644 --- a/dwctl/CHANGELOG.md +++ b/dwctl/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [0.11.0](https://github.com/doublewordai/control-layer/compare/v0.10.1...v0.11.0) (2025-11-28) + + +### Features + +* **deps:** bump fusillade to 0.4.0 ([#279](https://github.com/doublewordai/control-layer/issues/279)) ([b47410a](https://github.com/doublewordai/control-layer/commit/b47410afb7df7bc865a74a9638d9283151b605cb)) +* make default user roles configurable via auth.default_user_roles ([#253](https://github.com/doublewordai/control-layer/issues/253)) ([f290c44](https://github.com/doublewordai/control-layer/commit/f290c44e193b155a71e440f5c83b21156a3856ed)) + + +### Bug Fixes + +* **deps:** update rust crate prometheus to 0.14 ([#273](https://github.com/doublewordai/control-layer/issues/273)) ([7dc6fb0](https://github.com/doublewordai/control-layer/commit/7dc6fb00c4a73193f8a0c57034bf1a737b9bcd83)) + ## [0.10.1](https://github.com/doublewordai/control-layer/compare/v0.10.0...v0.10.1) (2025-11-27) diff --git a/dwctl/Cargo.lock b/dwctl/Cargo.lock index a0087ddc2..08b446bfd 100644 --- a/dwctl/Cargo.lock +++ b/dwctl/Cargo.lock @@ -1178,7 +1178,7 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dwctl" -version = "0.10.1" +version = "0.11.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/dwctl/Cargo.toml b/dwctl/Cargo.toml index 265e41339..783a6327d 100644 --- a/dwctl/Cargo.toml +++ b/dwctl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dwctl" -version = "0.10.1" +version = "0.11.0" edition = "2024" description = "The Doubleword Control Layer - A self-hostable observability and analytics platform for LLM applications" license = "MIT OR Apache-2.0" From 01f2ed8c6c836679399b825995b4d6070f02f9ea Mon Sep 17 00:00:00 2001 From: Fergus Date: Fri, 28 Nov 2025 11:07:50 +0000 Subject: [PATCH 18/19] fix: update cargo.lock --- Cargo.lock | 491 ++++++++++++++++++++++++++--------------------------- 1 file changed, 242 insertions(+), 249 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfdbf23e1..adbe43e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,22 +152,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -272,7 +272,7 @@ checksum = "0289cba6d5143bfe8251d57b4a8cac036adf158525a76533a7082ba65ec76398" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -294,7 +294,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -329,7 +329,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -378,7 +378,7 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "itoa", @@ -397,18 +397,18 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" dependencies = [ "axum-core 0.5.5", "bytes", "form_urlencoded", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "itoa", "matchit 0.8.4", @@ -438,7 +438,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -457,7 +457,7 @@ checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", @@ -474,10 +474,10 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e1a6651f119707ec6c416f38fdb5be223707627fe6d47f912850634c106215" dependencies = [ - "axum 0.8.6", + "axum 0.8.7", "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "matchit 0.8.4", "metrics", @@ -494,10 +494,10 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb15221c30bbb32e99873d348d89d7bc2138d6199520aa473a1bdb0d8e5721e8" dependencies = [ - "axum 0.8.6", + "axum 0.8.7", "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "matchit 0.8.4", "metrics", @@ -517,13 +517,13 @@ dependencies = [ "anyhow", "assert-json-diff", "auto-future", - "axum 0.8.6", + "axum 0.8.7", "bytes", "bytesize", "cookie", - "http 1.3.1", + "http 1.4.0", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "mime", "pretty_assertions", @@ -652,14 +652,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] name = "borsh" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ "borsh-derive", "cfg_aliases", @@ -667,15 +667,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -741,15 +741,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytesize" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" [[package]] name = "cast" @@ -759,9 +759,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.44" +version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "shlex", @@ -842,9 +842,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -852,9 +852,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -871,7 +871,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -938,9 +938,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] @@ -962,26 +962,22 @@ dependencies = [ [[package]] name = "criterion" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "futures", - "is-terminal", - "itertools 0.10.5", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "tokio", @@ -990,12 +986,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", - "itertools 0.10.5", + "itertools 0.13.0", ] [[package]] @@ -1049,9 +1045,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1098,7 +1094,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1112,7 +1108,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1123,7 +1119,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1134,7 +1130,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1209,7 +1205,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1219,7 +1215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1248,7 +1244,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1267,7 +1263,7 @@ dependencies = [ "async-openai", "async-stripe", "async-trait", - "axum 0.8.6", + "axum 0.8.7", "axum-prometheus 0.9.0", "axum-test", "base64 0.22.1", @@ -1497,9 +1493,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" @@ -1534,6 +1530,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1575,9 +1577,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "fusillade" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e83937d9be5a0f87a10affa73e7c73d049838f20fc5c6778e3daa68b3db688b" +checksum = "353a020d7b0cf34848f27a1169eef4cf5f090ee93f34aa7f329ff9b195f84b60" dependencies = [ "anyhow", "async-trait", @@ -1587,12 +1589,14 @@ dependencies = [ "hostname", "humantime", "parking_lot 0.12.5", + "rand 0.8.5", "reqwest", "scopeguard", "serde", "serde_json", "sqlx", - "thiserror 1.0.69", + "test-log", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -1682,7 +1686,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -1723,9 +1727,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1793,9 +1797,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "governor" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19" +checksum = "6e23d5986fd4364c2fb7498523540618b4b8d92eec6c36a02e565f66748e2f79" dependencies = [ "cfg-if", "dashmap", @@ -1803,7 +1807,7 @@ dependencies = [ "futures-timer", "futures-util", "getrandom 0.3.4", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "nonzero_ext", "parking_lot 0.12.5", "portable-atomic", @@ -1826,7 +1830,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.12.0", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -1844,8 +1848,8 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.12.0", + "http 1.4.0", + "indexmap 2.12.1", "slab", "tokio", "tokio-util", @@ -1890,14 +1894,19 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "hashlink" @@ -1977,12 +1986,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -2004,7 +2012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -2015,7 +2023,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "pin-project-lite", ] @@ -2101,16 +2109,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2 0.4.12", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "httparse", "httpdate", @@ -2128,8 +2136,8 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.3.1", - "hyper 1.7.0", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", "rustls", "rustls-pki-types", @@ -2145,7 +2153,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -2173,7 +2181,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -2183,18 +2191,18 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", - "hyper 1.7.0", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", @@ -2352,12 +2360,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -2431,17 +2439,6 @@ dependencies = [ "serde", ] -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2450,9 +2447,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2474,9 +2471,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -2675,7 +2672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" dependencies = [ "base64 0.22.1", - "indexmap 2.12.0", + "indexmap 2.12.1", "metrics", "metrics-util 0.19.1", "quanta", @@ -2689,7 +2686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" dependencies = [ "base64 0.22.1", - "indexmap 2.12.0", + "indexmap 2.12.1", "metrics", "metrics-util 0.20.0", "quanta", @@ -2802,7 +2799,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http 1.4.0", "httparse", "memchr", "mime", @@ -2897,9 +2894,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", @@ -3005,14 +3002,14 @@ checksum = "33bf71f8688855bf8419d9a75f8f6446c1fb8c98b8b7082eeda5240d6480bfd8" dependencies = [ "anyhow", "async-trait", - "axum 0.8.6", + "axum 0.8.7", "axum-prometheus 0.8.0", "bon", "clap", "dashmap", "futures-util", "governor", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-tls 0.6.0", "hyper-util", "notify", @@ -3040,9 +3037,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags 2.10.0", "cfg-if", @@ -3061,7 +3058,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -3072,9 +3069,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -3104,7 +3101,7 @@ checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80" dependencies = [ "async-trait", "bytes", - "http 1.3.1", + "http 1.4.0", "opentelemetry", "reqwest", ] @@ -3117,7 +3114,7 @@ checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" dependencies = [ "async-trait", "futures-core", - "http 1.3.1", + "http 1.4.0", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -3171,7 +3168,7 @@ checksum = "b71c5ba1c445cf62752a81ac699c113109bbb4bfae9f7d01c30e664537ec59d9" dependencies = [ "anyhow", "async-stream", - "axum 0.8.6", + "axum 0.8.7", "bytes", "futures", "http-body-util", @@ -3197,7 +3194,7 @@ dependencies = [ "base64 0.22.1", "bytes", "chrono", - "http 1.3.1", + "http 1.4.0", "outlet", "serde", "serde_json", @@ -3299,7 +3296,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -3344,7 +3341,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -3531,7 +3528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -3560,16 +3557,16 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", "version_check", "yansi", ] [[package]] name = "prometheus" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" dependencies = [ "cfg-if", "fnv", @@ -3577,7 +3574,7 @@ dependencies = [ "memchr", "parking_lot 0.12.5", "protobuf", - "thiserror 1.0.69", + "thiserror 2.0.17", ] [[package]] @@ -3600,14 +3597,28 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] name = "protobuf" -version = "2.28.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] [[package]] name = "psm" @@ -3711,9 +3722,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -3909,7 +3920,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -3969,10 +3980,10 @@ dependencies = [ "futures-core", "futures-util", "h2 0.4.12", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-rustls", "hyper-tls 0.6.0", "hyper-util", @@ -4029,7 +4040,7 @@ checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" dependencies = [ "anyhow", "async-trait", - "http 1.3.1", + "http 1.4.0", "reqwest", "serde", "thiserror 1.0.69", @@ -4046,8 +4057,8 @@ dependencies = [ "async-trait", "futures", "getrandom 0.2.16", - "http 1.3.1", - "hyper 1.7.0", + "http 1.4.0", + "hyper 1.8.1", "parking_lot 0.11.2", "reqwest", "reqwest-middleware", @@ -4067,7 +4078,7 @@ dependencies = [ "anyhow", "async-trait", "getrandom 0.2.16", - "http 1.3.1", + "http 1.4.0", "matchit 0.8.4", "reqwest", "reqwest-middleware", @@ -4137,9 +4148,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -4175,7 +4186,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.109", + "syn 2.0.111", "walkdir", ] @@ -4198,7 +4209,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "mime", "rand 0.9.2", "thiserror 2.0.17", @@ -4271,9 +4282,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" dependencies = [ "web-time", "zeroize", @@ -4437,7 +4448,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -4500,15 +4511,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.12.1", "schemars 0.9.0", "schemars 1.1.0", "serde_core", @@ -4519,14 +4530,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -4535,7 +4546,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "itoa", "ryu", "serde", @@ -4564,7 +4575,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -4606,9 +4617,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] @@ -4768,7 +4779,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap 2.12.0", + "indexmap 2.12.1", "log", "memchr", "native-tls", @@ -4799,7 +4810,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -4822,7 +4833,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.109", + "syn 2.0.111", "tokio", "url", ] @@ -4993,9 +5004,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -5019,7 +5030,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5087,9 +5098,9 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" +checksum = "37d53ac171c92a39e4769491c4b4dde7022c60042254b5fc044ae409d34a24d4" dependencies = [ "env_logger", "test-log-macros", @@ -5098,13 +5109,13 @@ dependencies = [ [[package]] name = "test-log-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36" +checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5133,7 +5144,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5144,7 +5155,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5247,7 +5258,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5323,7 +5334,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "toml_datetime", "toml_parser", "winnow", @@ -5350,10 +5361,10 @@ dependencies = [ "base64 0.22.1", "bytes", "h2 0.4.12", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -5406,15 +5417,15 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ "bitflags 2.10.0", "bytes", "futures-core", "futures-util", - "http 1.3.1", + "http 1.4.0", "http-body 1.0.1", "http-body-util", "http-range-header", @@ -5446,9 +5457,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -5458,20 +5469,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -5508,9 +5519,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -5630,7 +5641,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "serde", "serde_json", "utoipa-gen", @@ -5645,7 +5656,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.109", + "syn 2.0.111", "uuid", ] @@ -5655,7 +5666,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5f8f5abd341cce16bb4f09a8bafc087d4884a004f25fb980e538d51d6501dab" dependencies = [ - "axum 0.8.6", + "axum 0.8.7", "serde", "serde_json", "utoipa", @@ -5745,9 +5756,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -5758,9 +5769,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -5771,9 +5782,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5781,22 +5792,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -5831,9 +5842,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -5917,8 +5928,8 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-result", + "windows-strings", ] [[package]] @@ -5929,7 +5940,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5940,7 +5951,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -5957,22 +5968,13 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-result" -version = "0.3.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", + "windows-link 0.2.1", + "windows-result", + "windows-strings", ] [[package]] @@ -5984,15 +5986,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-strings" version = "0.5.1" @@ -6235,9 +6228,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -6252,9 +6245,9 @@ dependencies = [ "base64 0.22.1", "deadpool", "futures", - "http 1.3.1", + "http 1.4.0", "http-body-util", - "hyper 1.7.0", + "hyper 1.8.1", "hyper-util", "log", "once_cell", @@ -6321,28 +6314,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] [[package]] @@ -6362,7 +6355,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", "synstructure", ] @@ -6402,5 +6395,5 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn 2.0.111", ] From 57604c1e45d779028ca6a0648c0d2197d15d682f Mon Sep 17 00:00:00 2001 From: Fergus Date: Fri, 28 Nov 2025 11:17:26 +0000 Subject: [PATCH 19/19] chore: formatting --- dwctl/src/api/handlers/files.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/dwctl/src/api/handlers/files.rs b/dwctl/src/api/handlers/files.rs index 3e6585174..5b1fa45b8 100644 --- a/dwctl/src/api/handlers/files.rs +++ b/dwctl/src/api/handlers/files.rs @@ -228,15 +228,11 @@ fn create_file_stream( let error_start = valid_up_to.saturating_sub(20); let error_end = (valid_up_to + error_len + 20).min(combined_bytes.len()); let problem_bytes = &combined_bytes[error_start..error_end]; - tracing::error!( - "Bytes around error (offset {}-{}): {:02x?}", - error_start, - error_end, - problem_bytes - ); + tracing::error!("Bytes around error (offset {}-{}): {:02x?}", error_start, error_end, problem_bytes); // Try to show ASCII representation - let ascii_repr: String = problem_bytes.iter() + let ascii_repr: String = problem_bytes + .iter() .map(|&b| if b.is_ascii_graphic() || b == b' ' { b as char } else { '.' }) .collect(); tracing::error!("ASCII representation: '{}'", ascii_repr); @@ -255,16 +251,14 @@ fn create_file_stream( total_size - chunk_size + valid_up_to as i64, e ); - let _ = tx - .send(fusillade::FileStreamItem::Error(error_msg)) - .await; + let _ = tx.send(fusillade::FileStreamItem::Error(error_msg)).await; return; } // Otherwise, this is an incomplete UTF-8 sequence at the end of the chunk // Save the incomplete bytes for the next chunk - let valid_str = std::str::from_utf8(&combined_bytes[..valid_up_to]) - .expect("valid_up_to should point to valid UTF-8"); + let valid_str = + std::str::from_utf8(&combined_bytes[..valid_up_to]).expect("valid_up_to should point to valid UTF-8"); let remaining = combined_bytes[valid_up_to..].to_vec(); tracing::debug!(