routa_server/api/
acp_docker.rs1use axum::{
7 extract::{Query, State},
8 routing::{get, post},
9 Json, Router,
10};
11use serde::{Deserialize, Serialize};
12
13use crate::error::ServerError;
14use crate::state::AppState;
15use routa_core::acp::docker::{DockerContainerConfig, DockerStatus};
16
17pub fn router() -> Router<AppState> {
18 Router::new()
19 .route("/status", get(docker_status))
20 .route("/pull", post(docker_pull))
21 .route("/containers", get(list_containers))
22 .route("/container/start", post(start_container))
23 .route("/container/stop", post(stop_container))
24}
25
26#[derive(Debug, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct DockerStatusQuery {
30 #[serde(default)]
31 force_refresh: bool,
32}
33
34async fn docker_status(
36 State(state): State<AppState>,
37 Query(query): Query<DockerStatusQuery>,
38) -> Result<Json<DockerStatus>, ServerError> {
39 let status = state
40 .docker_state
41 .detector
42 .check_availability(query.force_refresh)
43 .await;
44
45 Ok(Json(status))
46}
47
48#[derive(Debug, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct DockerPullRequest {
52 pub image: String,
53}
54
55async fn docker_pull(
57 State(state): State<AppState>,
58 Json(body): Json<DockerPullRequest>,
59) -> Result<Json<serde_json::Value>, ServerError> {
60 let result = state.docker_state.detector.pull_image(&body.image).await;
61
62 Ok(Json(serde_json::json!({
63 "ok": result.ok,
64 "image": result.image,
65 "output": result.output,
66 "error": result.error,
67 })))
68}
69
70async fn list_containers(
72 State(state): State<AppState>,
73) -> Result<Json<serde_json::Value>, ServerError> {
74 let containers = state.docker_state.process_manager.list_containers().await;
75
76 Ok(Json(serde_json::json!({
77 "containers": containers,
78 })))
79}
80
81#[derive(Debug, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct StartContainerRequest {
85 pub session_id: String,
86 pub image: String,
87 pub workspace_path: String,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub auth_json: Option<String>,
90}
91
92async fn start_container(
94 State(state): State<AppState>,
95 Json(body): Json<StartContainerRequest>,
96) -> Result<Json<serde_json::Value>, ServerError> {
97 let config = DockerContainerConfig {
98 session_id: body.session_id.clone(),
99 image: body.image,
100 workspace_path: body.workspace_path,
101 env: None,
102 additional_volumes: None,
103 labels: None,
104 container_port: None,
105 auth_json: body.auth_json,
106 };
107
108 match state
109 .docker_state
110 .process_manager
111 .start_container(config)
112 .await
113 {
114 Ok(info) => Ok(Json(serde_json::json!({
115 "ok": true,
116 "container": info,
117 }))),
118 Err(e) => Ok(Json(serde_json::json!({
119 "ok": false,
120 "error": e,
121 }))),
122 }
123}
124
125#[derive(Debug, Deserialize, Serialize)]
127#[serde(rename_all = "camelCase")]
128pub struct StopContainerRequest {
129 pub session_id: String,
130}
131
132async fn stop_container(
134 State(state): State<AppState>,
135 Json(body): Json<StopContainerRequest>,
136) -> Result<Json<serde_json::Value>, ServerError> {
137 match state
138 .docker_state
139 .process_manager
140 .stop_container(&body.session_id)
141 .await
142 {
143 Ok(()) => Ok(Json(serde_json::json!({ "ok": true }))),
144 Err(e) => Ok(Json(serde_json::json!({ "ok": false, "error": e }))),
145 }
146}