huskies: merge 569_story_gateway_ui_cross_project_pipeline_status_view
This commit is contained in:
@@ -1313,6 +1313,66 @@ fn toml_string(s: &str) -> String {
|
||||
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
|
||||
}
|
||||
|
||||
/// `GET /api/gateway/pipeline` — fetch pipeline status from all registered projects.
|
||||
///
|
||||
/// Returns `{ "active": "<project>", "projects": { "<name>": { "active": [...], "backlog": [...], "backlog_count": N } | { "error": "..." } } }`.
|
||||
#[handler]
|
||||
pub async fn gateway_all_pipeline_handler(state: Data<&Arc<GatewayState>>) -> Response {
|
||||
let project_entries: Vec<(String, String)> = state
|
||||
.projects
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.map(|(n, e)| (n.clone(), e.url.clone()))
|
||||
.collect();
|
||||
|
||||
let mut results: BTreeMap<String, Value> = BTreeMap::new();
|
||||
|
||||
for (name, url) in &project_entries {
|
||||
let mcp_url = format!("{}/mcp", url.trim_end_matches('/'));
|
||||
let rpc_body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_pipeline_status",
|
||||
"arguments": {}
|
||||
}
|
||||
});
|
||||
|
||||
let status = match state.client.post(&mcp_url).json(&rpc_body).send().await {
|
||||
Ok(resp) => match resp.json::<Value>().await {
|
||||
Ok(upstream) => {
|
||||
// The tool result is a JSON string embedded in content[0].text.
|
||||
if let Some(text) = upstream
|
||||
.get("result")
|
||||
.and_then(|r| r.get("content"))
|
||||
.and_then(|c| c.get(0))
|
||||
.and_then(|c| c.get("text"))
|
||||
.and_then(|t| t.as_str())
|
||||
{
|
||||
serde_json::from_str(text)
|
||||
.unwrap_or_else(|_| json!({ "error": "invalid pipeline json" }))
|
||||
} else {
|
||||
json!({ "error": "unexpected response shape" })
|
||||
}
|
||||
}
|
||||
Err(e) => json!({ "error": format!("invalid response: {e}") }),
|
||||
},
|
||||
Err(e) => json!({ "error": format!("unreachable: {e}") }),
|
||||
};
|
||||
|
||||
results.insert(name.clone(), status);
|
||||
}
|
||||
|
||||
let active = state.active_project.read().await.clone();
|
||||
let body = json!({ "active": active, "projects": results });
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&body).unwrap_or_default()))
|
||||
}
|
||||
|
||||
/// `GET /api/gateway/bot-config` — return current bot.toml fields as JSON.
|
||||
#[handler]
|
||||
pub async fn gateway_bot_config_get_handler(state: Data<&Arc<GatewayState>>) -> Response {
|
||||
@@ -1622,6 +1682,10 @@ pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||
.at("/bot-config", poem::get(gateway_bot_config_page_handler))
|
||||
.at("/api/gateway", poem::get(gateway_api_handler))
|
||||
.at("/api/gateway/switch", poem::post(gateway_switch_handler))
|
||||
.at(
|
||||
"/api/gateway/pipeline",
|
||||
poem::get(gateway_all_pipeline_handler),
|
||||
)
|
||||
.at(
|
||||
"/api/gateway/projects",
|
||||
poem::post(gateway_add_project_handler),
|
||||
|
||||
Reference in New Issue
Block a user