171 lines
4.4 KiB
Rust
171 lines
4.4 KiB
Rust
use crate::{
|
|
http::{
|
|
error::AppError,
|
|
response::{reply, HandlerResponse},
|
|
},
|
|
node::{
|
|
container::ContainerInfo,
|
|
stack::{
|
|
check_compose, command, commit_compose, create_new, get_compose, get_containers,
|
|
write_compose, StackCommand,
|
|
},
|
|
},
|
|
AppState,
|
|
};
|
|
use askama::Template;
|
|
use askama_axum::IntoResponse;
|
|
use axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
response::Redirect,
|
|
routing::{get, post},
|
|
Form, Router,
|
|
};
|
|
use futures_util::join;
|
|
use serde::Deserialize;
|
|
use serde_json::json;
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "stack/get-one.html")]
|
|
struct GetOneTemplate {
|
|
stack_name: String,
|
|
file_contents: String,
|
|
containers: Vec<ContainerInfo>,
|
|
}
|
|
|
|
#[derive(Template)]
|
|
#[template(path = "stack/new-form.html")]
|
|
struct CreateTemplate {}
|
|
|
|
async fn get_one(Path(stack_name): Path<String>, State(state): State<AppState>) -> HandlerResponse {
|
|
let (file_contents_res, containers_res) = join!(
|
|
get_compose(&state.stack_dir, &stack_name),
|
|
get_containers(&state.docker, &stack_name)
|
|
);
|
|
|
|
let file_contents = file_contents_res?;
|
|
let containers = containers_res?;
|
|
|
|
reply(
|
|
json!({
|
|
"name": stack_name,
|
|
"file": file_contents,
|
|
"containers": containers,
|
|
}),
|
|
GetOneTemplate {
|
|
stack_name,
|
|
file_contents,
|
|
containers: containers.iter().map(|c| c.clone().into()).collect(),
|
|
},
|
|
)
|
|
}
|
|
|
|
async fn new_stack_page() -> impl IntoResponse {
|
|
CreateTemplate {}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct CreateStackForm {
|
|
name: String,
|
|
source: String,
|
|
}
|
|
|
|
async fn create_stack(
|
|
State(state): State<AppState>,
|
|
Form(form): Form<CreateStackForm>,
|
|
) -> Result<Redirect, AppError> {
|
|
// Make sure body is is ok
|
|
check_compose(&state.arion_bin, &form.source).await?;
|
|
|
|
create_new(state.repository, &form.name, &form.source).await?;
|
|
|
|
Ok(Redirect::to(format!("/stack/{}/", form.name).as_str()))
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct EditStackForm {
|
|
source: String,
|
|
commit_message: String,
|
|
}
|
|
|
|
async fn edit_stack(
|
|
Path(stack_name): Path<String>,
|
|
State(state): State<AppState>,
|
|
Form(form): Form<EditStackForm>,
|
|
) -> Result<Redirect, AppError> {
|
|
let commit_message = if form.commit_message.trim().is_empty() {
|
|
format!("Update {}", stack_name)
|
|
} else {
|
|
form.commit_message
|
|
};
|
|
|
|
if form.source.trim().is_empty() {
|
|
return Err(AppError::Client {
|
|
status: StatusCode::BAD_REQUEST,
|
|
code: "invalid-source",
|
|
message: "provided stack source is empty".to_string(),
|
|
});
|
|
};
|
|
|
|
// Cleanup source (like line endings)
|
|
let source = form.source.replace("\r\n", "\n");
|
|
|
|
// Make sure file is ok
|
|
check_compose(&state.arion_bin, &source).await?;
|
|
|
|
// Write compose file
|
|
write_compose(&state.stack_dir, &stack_name, &source).await?;
|
|
|
|
// Git commit
|
|
commit_compose(state.repository, &stack_name, &commit_message)?;
|
|
|
|
// Update stack
|
|
command(
|
|
&state.stack_dir,
|
|
&stack_name,
|
|
&state.arion_bin,
|
|
StackCommand::Start,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Redirect::to("./"))
|
|
}
|
|
|
|
async fn check_stack_file(
|
|
State(state): State<AppState>,
|
|
body: String,
|
|
) -> Result<StatusCode, AppError> {
|
|
check_compose(&state.arion_bin, &body).await?;
|
|
Ok(StatusCode::NO_CONTENT)
|
|
}
|
|
|
|
macro_rules! stack_command {
|
|
($cmd: expr) => {
|
|
move |Path(stack_name): Path<String>, State(state): State<AppState>| async move {
|
|
command(&state.stack_dir, &stack_name, &state.arion_bin, $cmd).await?;
|
|
Ok(Redirect::to("./")) as Result<Redirect, AppError>
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(super) fn router() -> Router<AppState> {
|
|
Router::new()
|
|
.route("/_/new", get(new_stack_page).post(create_stack))
|
|
.route("/_/check", post(check_stack_file))
|
|
.route(
|
|
"/:stack",
|
|
get(|Path(stack_name): Path<String>| async move {
|
|
Redirect::permanent(format!("{}/", &stack_name).as_str())
|
|
}),
|
|
)
|
|
.route("/:stack/", get(get_one))
|
|
.route("/:stack/start", post(stack_command!(StackCommand::Start)))
|
|
.route(
|
|
"/:stack/restart",
|
|
post(stack_command!(StackCommand::Restart)),
|
|
)
|
|
.route("/:stack/stop", post(stack_command!(StackCommand::Stop)))
|
|
.route("/:stack/down", post(stack_command!(StackCommand::Down)))
|
|
.route("/:stack/edit", post(edit_stack))
|
|
}
|