kittynode_web/
lib.rs

1use axum::{
2    Router,
3    extract::{Path, Query},
4    http::StatusCode,
5    response::Json,
6    routing::{get, post},
7};
8use eyre::Result;
9use kittynode_core::api;
10use kittynode_core::api::types::{
11    Config, LogsQuery, OperationalState, Package, PackageConfig, PackageRuntimeState, SystemInfo,
12};
13use kittynode_core::api::{DEFAULT_WEB_PORT, DockerStartStatus, validate_web_port};
14use serde::Deserialize;
15use std::collections::HashMap;
16use std::net::{Ipv4Addr, SocketAddr};
17use tokio::net::TcpListener;
18
19pub async fn hello_world() -> &'static str {
20    "Hello World!"
21}
22
23pub async fn add_capability(Path(name): Path<String>) -> Result<StatusCode, (StatusCode, String)> {
24    api::add_capability(&name).map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
25    Ok(StatusCode::OK)
26}
27
28pub async fn remove_capability(
29    Path(name): Path<String>,
30) -> Result<StatusCode, (StatusCode, String)> {
31    api::remove_capability(&name)
32        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
33    Ok(StatusCode::OK)
34}
35
36pub async fn get_capabilities() -> Result<Json<Vec<String>>, (StatusCode, String)> {
37    api::get_capabilities()
38        .map(Json)
39        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
40}
41
42pub async fn get_packages() -> Result<Json<HashMap<String, Package>>, (StatusCode, String)> {
43    api::get_packages()
44        .map(Json)
45        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
46}
47
48pub async fn get_config() -> Result<Json<Config>, (StatusCode, String)> {
49    api::get_config()
50        .map(Json)
51        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
52}
53
54pub async fn install_package(Path(name): Path<String>) -> Result<StatusCode, (StatusCode, String)> {
55    api::install_package(&name)
56        .await
57        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
58    Ok(StatusCode::OK)
59}
60
61#[derive(Default, Deserialize)]
62pub struct DeletePackageQuery {
63    include_images: Option<bool>,
64}
65
66pub async fn delete_package(
67    Path(name): Path<String>,
68    Query(params): Query<DeletePackageQuery>,
69) -> Result<StatusCode, (StatusCode, String)> {
70    let include_images = params.include_images.unwrap_or(false);
71    api::delete_package(&name, include_images)
72        .await
73        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
74    Ok(StatusCode::OK)
75}
76
77pub async fn stop_package(Path(name): Path<String>) -> Result<StatusCode, (StatusCode, String)> {
78    api::stop_package(&name)
79        .await
80        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
81    Ok(StatusCode::OK)
82}
83
84pub async fn resume_package(Path(name): Path<String>) -> Result<StatusCode, (StatusCode, String)> {
85    api::resume_package(&name)
86        .await
87        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
88    Ok(StatusCode::OK)
89}
90
91#[derive(Deserialize)]
92pub struct RuntimeStatesRequest {
93    names: Vec<String>,
94}
95
96pub async fn get_package_runtime_state(
97    Path(name): Path<String>,
98) -> Result<Json<PackageRuntimeState>, (StatusCode, String)> {
99    api::get_package_runtime_state(&name)
100        .await
101        .map(Json)
102        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
103}
104
105pub async fn get_package_runtime_states(
106    Json(payload): Json<RuntimeStatesRequest>,
107) -> Result<Json<HashMap<String, PackageRuntimeState>>, (StatusCode, String)> {
108    api::get_packages_runtime_state(&payload.names)
109        .await
110        .map(Json)
111        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
112}
113
114pub async fn get_installed_packages() -> Result<Json<Vec<Package>>, (StatusCode, String)> {
115    api::get_installed_packages()
116        .await
117        .map(Json)
118        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
119}
120
121pub async fn is_docker_running() -> Result<StatusCode, (StatusCode, String)> {
122    match api::is_docker_running().await {
123        true => Ok(StatusCode::OK),
124        false => Err((
125            StatusCode::SERVICE_UNAVAILABLE,
126            "Docker is not running".to_string(),
127        )),
128    }
129}
130
131pub async fn init_kittynode() -> Result<StatusCode, (StatusCode, String)> {
132    api::init_kittynode().map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
133    Ok(StatusCode::OK)
134}
135
136pub async fn delete_kittynode() -> Result<StatusCode, (StatusCode, String)> {
137    api::delete_kittynode().map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
138    Ok(StatusCode::OK)
139}
140
141pub async fn get_system_info() -> Result<Json<SystemInfo>, (StatusCode, String)> {
142    api::get_system_info()
143        .map(Json)
144        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
145}
146
147pub async fn get_container_logs(
148    Path(container_name): Path<String>,
149    Query(params): Query<LogsQuery>,
150) -> Result<Json<Vec<String>>, (StatusCode, String)> {
151    api::get_container_logs(&container_name, params.tail)
152        .await
153        .map(Json)
154        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
155}
156
157pub async fn get_package_config(
158    Path(name): Path<String>,
159) -> Result<Json<PackageConfig>, (StatusCode, String)> {
160    api::get_package_config(&name)
161        .await
162        .map(Json)
163        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
164}
165
166pub async fn update_package_config(
167    Path(name): Path<String>,
168    Json(config): Json<PackageConfig>,
169) -> Result<StatusCode, (StatusCode, String)> {
170    api::update_package_config(&name, config)
171        .await
172        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
173    Ok(StatusCode::OK)
174}
175
176pub async fn start_docker_if_needed() -> Result<Json<DockerStartStatus>, (StatusCode, String)> {
177    api::start_docker_if_needed()
178        .await
179        .map(Json)
180        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
181}
182
183pub async fn get_operational_state() -> Result<Json<OperationalState>, (StatusCode, String)> {
184    api::get_operational_state()
185        .await
186        .map(Json)
187        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
188}
189
190pub fn app() -> Router {
191    Router::new()
192        .route("/", get(hello_world))
193        .route("/add_capability/{name}", post(add_capability))
194        .route("/remove_capability/{name}", post(remove_capability))
195        .route("/get_capabilities", get(get_capabilities))
196        .route("/get_packages", get(get_packages))
197        .route("/get_config", get(get_config))
198        .route("/install_package/{name}", post(install_package))
199        .route("/delete_package/{name}", post(delete_package))
200        .route("/stop_package/{name}", post(stop_package))
201        .route("/resume_package/{name}", post(resume_package))
202        .route("/get_installed_packages", get(get_installed_packages))
203        .route("/package_runtime", post(get_package_runtime_states))
204        .route("/package_runtime/{name}", get(get_package_runtime_state))
205        .route("/is_docker_running", get(is_docker_running))
206        .route("/init_kittynode", post(init_kittynode))
207        .route("/delete_kittynode", post(delete_kittynode))
208        .route("/get_system_info", get(get_system_info))
209        .route("/logs/{container_name}", get(get_container_logs))
210        .route("/get_package_config/{name}", get(get_package_config))
211        .route("/update_package_config/{name}", post(update_package_config))
212        .route("/start_docker_if_needed", post(start_docker_if_needed))
213        .route("/get_operational_state", get(get_operational_state))
214}
215
216pub async fn run() -> Result<()> {
217    let _ = tracing_subscriber::fmt::try_init();
218    run_with_port(DEFAULT_WEB_PORT).await
219}
220
221pub async fn run_with_port(port: u16) -> Result<()> {
222    validate_web_port(port)?;
223    let app = app();
224    let address = SocketAddr::from((Ipv4Addr::UNSPECIFIED, port));
225    let listener = TcpListener::bind(address).await?;
226    axum::serve(listener, app).await?;
227    Ok(())
228}