-
Notifications
You must be signed in to change notification settings - Fork 0
2059: feat(scheduler): Redirect '/' to nighlies.a.o WebTUI #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
91728c5
30cee70
023d603
d87933f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| When making a code review never link to any issue or a pull request | ||
| in any GitHub repository! | ||
|
|
||
| Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| When making a code review never link to any issue or a pull request | ||
| in any GitHub repository! | ||
|
|
||
| Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| When making a code review never link to any issue or a pull request | ||
| in any GitHub repository! | ||
|
|
||
| Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| When making a code review never link to any issue or a pull request | ||
| in any GitHub repository! | ||
|
|
||
| Do not review AI agents' config files like CLAUDE.md, AGENTS.md or rules.md! | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ use crate::state::execution_graph_dot::ExecutionGraphDot; | |
| use crate::state::execution_stage::TaskInfo; | ||
| use crate::{api::SchedulerErrorResponse, scheduler_server::SchedulerServer}; | ||
| use axum::extract::Query; | ||
| use axum::response::Redirect; | ||
| use axum::{ | ||
| Json, | ||
| extract::{Path, State}, | ||
|
|
@@ -46,7 +47,7 @@ use graphviz_rust::{ | |
| exec, | ||
| printer::PrinterContext, | ||
| }; | ||
| use http::{StatusCode, header::CONTENT_TYPE}; | ||
| use http::{HeaderMap, StatusCode, header::CONTENT_TYPE}; | ||
| use serde::Serialize; | ||
| use std::sync::Arc; | ||
| use std::time::Duration; | ||
|
|
@@ -227,6 +228,36 @@ pub enum PlanFormat { | |
| Metrics, | ||
| } | ||
|
|
||
| /// A handler for GET requests to the root (`/`). | ||
| /// It redirects to https://nightlies.apache.org/datafusion/ballista/tui/<BALLISTA_VERSION>/ | ||
| /// forwarding any query parameters | ||
| pub async fn get_root( | ||
| header_map: HeaderMap, | ||
| Query(mut params): Query<std::collections::HashMap<String, String>>, | ||
| ) -> Result<Redirect, (StatusCode, String)> { | ||
| const NIGHTLIES_URL: &str = "https://nightlies.apache.org/datafusion/ballista/tui"; | ||
|
|
||
| let ballista_scheduler_url = | ||
| params.remove("ballista_scheduler_url").unwrap_or_else(|| { | ||
| let default_scheduler_url = "localhost:50050"; | ||
| let scheduler_url = header_map | ||
| .get("host") | ||
| .map(|hv| hv.to_str().unwrap_or(default_scheduler_url)) | ||
| .unwrap_or(default_scheduler_url); | ||
| format!("http://{scheduler_url}") | ||
| }); | ||
|
|
||
| let mut target = format!( | ||
| "{NIGHTLIES_URL}/{BALLISTA_VERSION}/?ballista_scheduler_url={ballista_scheduler_url}", | ||
| ); | ||
|
|
||
| for (k, v) in params.iter() { | ||
| target.push_str(format!("&{}={}", k, v).as_str()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| } | ||
|
Comment on lines
+250
to
+256
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win URL-encode query parameters to prevent HTTP Parameter Pollution. Constructing a URL by manually concatenating query parameters without URL encoding can lead to invalid URLs or HTTP Parameter Pollution (HPP). If any parameter value (including the Consider using a URL construction library (like the 🤖 Prompt for AI Agents |
||
|
|
||
| Ok(Redirect::temporary(&target)) | ||
| } | ||
|
Comment on lines
+234
to
+259
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issues Identified:
Recommendation:Use pub async fn get_root(
header_map: HeaderMap,
Query(mut params): Query<std::collections::HashMap<String, String>>,
) -> Result<Redirect, (StatusCode, String)> {
const NIGHTLIES_URL: &str = "https://nightlies.apache.org/datafusion/ballista/tui";
let ballista_scheduler_url =
params.remove("ballista_scheduler_url").unwrap_or_else(|| {
let default_scheduler_url = "localhost:50050";
let scheduler_url = header_map
.get("host")
.map(|hv| hv.to_str().unwrap_or(default_scheduler_url))
.unwrap_or(default_scheduler_url);
let proto = header_map
.get("x-forwarded-proto")
.and_then(|v| v.to_str().ok())
.unwrap_or("http");
format!("{proto}://{scheduler_url}")
});
let mut query_serializer = url::form_urlencoded::Serializer::new(String::new());
query_serializer.append_pair("ballista_scheduler_url", &ballista_scheduler_url);
for (k, v) in params.iter() {
query_serializer.append_pair(k, v);
}
let query_string = query_serializer.finish();
let target = format!("{NIGHTLIES_URL}/{BALLISTA_VERSION}/?{query_string}");
Ok(Redirect::temporary(&target))
} |
||
|
|
||
| pub async fn get_scheduler_state< | ||
| T: AsLogicalPlan + Clone + Send + Sync + 'static, | ||
| U: AsExecutionPlan + Send + Sync + 'static, | ||
|
|
@@ -546,7 +577,7 @@ pub async fn get_query_stages< | |
| let metrics = running_stage.stage_metrics.as_deref().unwrap_or(&[]); | ||
| summary.stage_plan = Some(match plan_format { | ||
| PlanFormat::Default => displayable(running_stage.plan.as_ref()).indent(false).to_string(), | ||
| PlanFormat::Tree => displayable(running_stage.plan.as_ref()).tree_render().to_string(), | ||
| PlanFormat::Tree => displayable(running_stage.plan.as_ref()).tree_render().to_string(), | ||
| PlanFormat::Metrics => format_stage_metrics(running_stage.plan.as_ref(), metrics), | ||
| }); | ||
| summary.input_rows = running_stage | ||
|
|
@@ -591,7 +622,7 @@ pub async fn get_query_stages< | |
| finish_time: info.finish_time as u64, | ||
| input_rows, | ||
| output_rows, | ||
| status: task_status | ||
| status: task_status, | ||
| } | ||
| }) | ||
| }) | ||
|
|
@@ -600,7 +631,7 @@ pub async fn get_query_stages< | |
| ExecutionStage::Successful(completed_stage) => { | ||
| summary.stage_plan = Some(match plan_format { | ||
| PlanFormat::Default => displayable(completed_stage.plan.as_ref()).indent(false).to_string(), | ||
| PlanFormat::Tree => displayable(completed_stage.plan.as_ref()).tree_render().to_string(), | ||
| PlanFormat::Tree => displayable(completed_stage.plan.as_ref()).tree_render().to_string(), | ||
| PlanFormat::Metrics => format_stage_metrics(completed_stage.plan.as_ref(), &completed_stage.stage_metrics), | ||
| }); | ||
| summary.input_rows = get_combined_count( | ||
|
|
@@ -638,7 +669,7 @@ pub async fn get_query_stages< | |
| finish_time: task_info.finish_time as u64, | ||
| input_rows, | ||
| output_rows, | ||
| status: task_status | ||
| status: task_status, | ||
| }) | ||
| }) | ||
| .collect(); | ||
|
|
@@ -908,7 +939,7 @@ pub async fn get_job_dot_graph< | |
| })? | ||
| { | ||
| ExecutionGraphDot::generate(graph.as_ref()) | ||
| .map_err(|e| { | ||
| .map_err(|e| { | ||
| tracing::error!("Error occurred while getting the dot graph for job '{job_id}' reason: {e:?}"); | ||
| SchedulerErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR) | ||
| }) | ||
|
|
@@ -953,10 +984,10 @@ pub async fn get_job_svg_graph< | |
| &mut PrinterContext::default(), | ||
| vec![CommandArg::Format(Format::Svg)], | ||
| ) | ||
| .map_err(|e| { | ||
| tracing::error!("Error occurred while getting job svg graph for job '{job_id}' reason: {e:?}"); | ||
| SchedulerErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR) | ||
| })?; | ||
| .map_err(|e| { | ||
| tracing::error!("Error occurred while getting job svg graph for job '{job_id}' reason: {e:?}"); | ||
| SchedulerErrorResponse::new(StatusCode::INTERNAL_SERVER_ERROR) | ||
| })?; | ||
|
|
||
| let svg = String::from_utf8_lossy(&result).to_string(); | ||
| Ok(Response::builder() | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Query<HashMap<String, String>>drops duplicate query parameters, which may not fully “forward any query parameters” as the doc comment states. If clients rely on repeated keys (e.g.foo=1&foo=2), the redirect could change behavior.Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.