Compare commits

...

2 commits

Author SHA1 Message Date
096d44ae59 add remove 2023-11-25 01:38:56 +01:00
ddb4ec4a5c css refactor 2023-11-25 01:29:19 +01:00
10 changed files with 248 additions and 141 deletions

View file

@ -1,5 +1,5 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use git2::{ErrorCode, IndexAddOption, Repository, Signature}; use git2::{ErrorCode, Index, IndexAddOption, Oid, Repository, Signature};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tracing::info; use tracing::info;
@ -40,9 +40,6 @@ impl ThreadSafeRepository {
index.add_all(["*/*.nix"].iter(), IndexAddOption::DEFAULT, None)?; index.add_all(["*/*.nix"].iter(), IndexAddOption::DEFAULT, None)?;
let oid = index.write_tree()?; let oid = index.write_tree()?;
let tree = repo.find_tree(oid)?; let tree = repo.find_tree(oid)?;
// This prevents a nasty condition where the index goes all wack,
// but it's probably a mistake somewhere else
index.write()?; index.write()?;
let signature = Signature::now(&config.author_name, &config.author_email)?; let signature = Signature::now(&config.author_name, &config.author_email)?;
@ -71,24 +68,15 @@ impl ThreadSafeRepository {
Repository::open(&self.path) Repository::open(&self.path)
} }
pub fn commit_files(&self, paths: &[&Path], message: &str) -> Result<()> { fn commit(&self, repository: Repository, mut index: Index, message: &str) -> Result<Oid> {
let repository = self.repository()?; index.write()?;
// Commit file
let mut index = repository.index()?;
for path in paths {
index.add_path(path)?;
}
let oid = index.write_tree()?; let oid = index.write_tree()?;
let tree = repository.find_tree(oid)?; let tree = repository.find_tree(oid)?;
let head = repository.head()?; let head = repository.head()?;
// This prevents a nasty condition where the index goes all wack,
// but it's probably a mistake somewhere else
index.write()?;
let signature = Signature::now(&self.config.author_name, &self.config.author_email)?; let signature = Signature::now(&self.config.author_name, &self.config.author_email)?;
repository.commit( let oid = repository.commit(
Some("HEAD"), Some("HEAD"),
&signature, &signature,
&signature, &signature,
@ -98,6 +86,32 @@ impl ThreadSafeRepository {
)?; )?;
drop(tree); drop(tree);
Ok(oid)
}
pub fn commit_files(&self, paths: &[&Path], message: &str) -> Result<()> {
let repository = self.repository()?;
// Commit file
let mut index = repository.index()?;
for path in paths {
index.add_path(path)?;
}
self.commit(repository, index, message)?;
Ok(())
}
pub fn remove_folder(&self, path: &Path, message: &str) -> Result<()> {
let repository = self.repository()?;
// Commit file
let mut index = repository.index()?;
index.remove_dir(path, 0)?;
self.commit(repository, index, message)?;
Ok(()) Ok(())
} }
} }

View file

@ -186,6 +186,27 @@ pub async fn create_new(
Ok(()) Ok(())
} }
pub async fn remove(
base_dir: &Path,
arion_bin: &Path,
repository: ThreadSafeRepository,
stack_name: &str,
) -> Result<()> {
// Remove all containers and resources
command(base_dir, stack_name, arion_bin, StackCommand::Down).await?;
// Remove from repository
repository.remove_folder(
&PathBuf::from(stack_name),
format!("Removed stack {}", stack_name).as_str(),
)?;
// Remove from disk
fs::remove_dir_all(repository.path.join(stack_name)).await?;
Ok(())
}
pub async fn check_compose(arion_bin: &Path, source: &str) -> Result<()> { pub async fn check_compose(arion_bin: &Path, source: &str) -> Result<()> {
// Check that it's a valid nix tree // Check that it's a valid nix tree
rnix::Root::parse(source) rnix::Root::parse(source)

View file

@ -7,7 +7,7 @@ use crate::{
container::ContainerInfo, container::ContainerInfo,
stack::{ stack::{
check_compose, command, commit_compose, create_new, get_compose, get_containers, check_compose, command, commit_compose, create_new, get_compose, get_containers,
write_compose, StackCommand, remove, write_compose, StackCommand,
}, },
}, },
AppState, AppState,
@ -37,6 +37,12 @@ struct GetOneTemplate {
#[template(path = "stack/new-form.html")] #[template(path = "stack/new-form.html")]
struct CreateTemplate {} struct CreateTemplate {}
#[derive(Template)]
#[template(path = "stack/delete-one.html")]
struct ConfirmDeleteTemplate {
stack_name: String,
}
async fn get_one(Path(stack_name): Path<String>, State(state): State<AppState>) -> HandlerResponse { async fn get_one(Path(stack_name): Path<String>, State(state): State<AppState>) -> HandlerResponse {
let (file_contents_res, containers_res) = join!( let (file_contents_res, containers_res) = join!(
get_compose(&state.stack_dir, &stack_name), get_compose(&state.stack_dir, &stack_name),
@ -139,6 +145,24 @@ async fn check_stack_file(
Ok(StatusCode::NO_CONTENT) Ok(StatusCode::NO_CONTENT)
} }
async fn confirm_deletion_page(Path(stack_name): Path<String>) -> impl IntoResponse {
ConfirmDeleteTemplate { stack_name }
}
async fn delete_stack(
Path(stack_name): Path<String>,
State(state): State<AppState>,
) -> Result<Redirect, AppError> {
remove(
&state.stack_dir,
&state.arion_bin,
state.repository,
&stack_name,
)
.await?;
Ok(Redirect::to("/"))
}
macro_rules! stack_command { macro_rules! stack_command {
($cmd: expr) => { ($cmd: expr) => {
move |Path(stack_name): Path<String>, State(state): State<AppState>| async move { move |Path(stack_name): Path<String>, State(state): State<AppState>| async move {
@ -167,4 +191,8 @@ pub(super) fn router() -> Router<AppState> {
.route("/:stack/stop", post(stack_command!(StackCommand::Stop))) .route("/:stack/stop", post(stack_command!(StackCommand::Stop)))
.route("/:stack/down", post(stack_command!(StackCommand::Down))) .route("/:stack/down", post(stack_command!(StackCommand::Down)))
.route("/:stack/edit", post(edit_stack)) .route("/:stack/edit", post(edit_stack))
.route(
"/:stack/delete",
get(confirm_deletion_page).post(delete_stack),
)
} }

View file

@ -21,8 +21,8 @@
& .error { & .error {
border-radius: 3px; border-radius: 3px;
background-color: #500F1C; background-color: var(--danger-bg);
color: #FF9592; color: var(--danger-text);
padding: 4px 8px; padding: 4px 8px;
display: none; display: none;
} }
@ -35,14 +35,14 @@
.ace_editor { .ace_editor {
min-height: 50vh; min-height: 50vh;
border: 3px solid #5958B1; border: var(--table-border);
border-radius: 3px; border-radius: 3px;
&.checked { &.checked {
border-color: #46A758; border-color: var(--success-bright);
} }
&.err { &.err {
border-color: #E5484D; border-color: var(--danger-bright);
} }
} }

107
static/css/resource.css Normal file
View file

@ -0,0 +1,107 @@
.actions {
display: flex;
gap: 0.5rem;
& form {
display: flex;
min-width: 80px;
}
& button {
flex: 1;
&.danger {
background-color: var(--danger-bg);
border-color: var(--danger-bright);
color: var(--danger-bright);
&:hover {
border-color: var(--link);
color: var(--link);
background-color: var(--danger-bright);
}
}
}
&.big button {
font-size: 14pt;
padding: 8px 20px;
}
}
dd~dt,
dd~dd {
border-top: 1px solid #262A65;
}
dd[data-state] {
background-color: var(--table-bg);
}
dd[data-state="running"] {
background-color: var(--success-bg);
color: var(--success-text);
}
dd[data-state="stopped"],
dd[data-state="exited"],
dd[data-state="dead"] {
background-color: var(--danger-bg);
color: var(--danger-text);
}
dt,
dd {
box-sizing: border-box;
padding: 4px;
}
dt {
padding-bottom: 0;
}
dd {
background-color: #171625;
padding: 4px 8px;
word-break: break-all;
}
dl.table {
border: 1px solid #3D3E82;
background-color: var(--table-bg);
margin: 0;
& dt {
float: left;
width: 25%;
}
& dd {
margin-left: 25%;
border-left: 1px dotted #3D3E82;
}
& dd:after {
content: "";
display: block;
clear: both;
}
}
dl.list {
border: 1px solid #3D3E82;
background-color: var(--table-bg);
margin: 0;
border-top: 0;
& dd {
margin: 0;
}
& dt {
padding: 4px 8px;
}
}

View file

@ -2,6 +2,11 @@
--background: #13131E; --background: #13131E;
--bg-raised: #171625; --bg-raised: #171625;
--text: #E0DFFE; --text: #E0DFFE;
--text-accent: #E796F3;
--table-border-color: #5958B1;
--table-border: 3px solid var(--table-border-color);
--table-bg: #202248;
--link: #FFC53D; --link: #FFC53D;
--link-hover: #e28d0e; --link-hover: #e28d0e;
@ -11,6 +16,14 @@
--button-border: #5B5BD6; --button-border: #5B5BD6;
--button-bg-hover: #7E451D; --button-bg-hover: #7E451D;
--success-bright: #46A758;
--success-bg: #113B29;
--success-text: #3DD68C;
--danger-bright: #E5484D;
--danger-bg: #500F1C;
--danger-text: #FF9592;
background-color: var(--background); background-color: var(--background);
color: var(--text); color: var(--text);
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
@ -64,7 +77,6 @@ nav {
} }
} }
main { main {
padding: 10px; padding: 10px;
} }
@ -92,7 +104,7 @@ button {
table.table { table.table {
background-color: var(--bg-raised); background-color: var(--bg-raised);
border: 3px solid #5958B1; border: var(--table-border);
border-radius: 3px; border-radius: 3px;
& th, & th,
@ -105,7 +117,7 @@ table.table {
} }
& thead { & thead {
background-color: #202248; background-color: var(--table-bg);
} }
} }
@ -116,7 +128,7 @@ table.table {
input, input,
textarea { textarea {
border: 1px solid #5958B1; border: 1px solid var(--table-border-color);
border-radius: 3px; border-radius: 3px;
background-color: var(--bg-raised); background-color: var(--bg-raised);
color: var(--text); color: var(--text);

View file

@ -71,6 +71,7 @@
</section> </section>
</main> </main>
<link rel="stylesheet" href="/static/css/resource.css" />
<style scoped> <style scoped>
a.stack-name { a.stack-name {
text-decoration: none; text-decoration: none;
@ -95,13 +96,13 @@
} }
.container-name { .container-name {
color: #E796F3; color: var(--text-accent);
} }
.label { .label {
& .key { & .key {
color: #E796F3; color: var(--text-accent);
} }
} }
@ -115,8 +116,8 @@
& .error { & .error {
padding: 4px 8px; padding: 4px 8px;
background-color: #3B1219; background-color: var(--danger-bg);
color: #FF9592; color: var(--danger-text);
} }
& p { & p {
@ -135,81 +136,6 @@
} }
} }
dd~dt,
dd~dd {
border-top: 1px solid #262A65;
}
dd[data-state] {
background-color: #202248;
}
dd[data-state="running"] {
background-color: #113B29;
color: #3DD68C;
}
dd[data-state="stopped"],
dd[data-state="exited"],
dd[data-state="dead"] {
background-color: #500F1C;
color: #FF9592;
}
dt,
dd {
box-sizing: border-box;
padding: 4px;
}
dt {
padding-bottom: 0;
}
dd {
background-color: #171625;
padding: 4px 8px;
word-break: break-all;
}
dl.table {
border: 1px solid #3D3E82;
background-color: #202248;
margin: 0;
& dt {
float: left;
width: 25%;
}
& dd {
margin-left: 25%;
border-left: 1px dotted #3D3E82;
}
& dd:after {
content: "";
display: block;
clear: both;
}
}
dl.list {
border: 1px solid #3D3E82;
background-color: #202248;
margin: 0;
border-top: 0;
& dd {
margin: 0;
}
& dt {
padding: 4px 8px;
}
}
main>header { main>header {
@ -244,20 +170,6 @@
overflow: auto; overflow: auto;
} }
} }
.actions {
display: flex;
gap: 0.5rem;
& form {
display: flex;
min-width: 80px;
}
& button {
flex: 1;
}
}
</style> </style>
<script type="module"> <script type="module">

View file

@ -158,11 +158,11 @@
padding: 0 10px; padding: 0 10px;
&.exited { &.exited {
background-color: #E5484D; background-color: var(--danger-bright);
} }
&.running { &.running {
background-color: #46A758; background-color: var(--success-bright);
} }
} }
} }
@ -180,7 +180,7 @@
&.status { &.status {
width: 6px; width: 6px;
padding: 0; padding: 0;
background-color: #E5484D; background-color: var(--danger-bright);
&.active { &.active {
background-color: #33B074; background-color: #33B074;
@ -210,11 +210,11 @@
} }
&.running::before { &.running::before {
color: #46A758; color: var(--success-bright);
} }
&.stopped::before { &.stopped::before {
color: #E5484D; color: var(--danger-bright);
} }
} }

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}Confirm deletion of stack {{stack_name}}?{% endblock %}
{% block content %}
<main>
<header>
<h1>Confirm deletion of stack <span class="stack-name">{{stack_name}}</span></h1>
<div class="actions big">
<form action="./delete" method="POST">
<button class="danger" type="submit">Delete</button>
</form>
<form action="." method="GET">
<button type="submit">Cancel</button>
</form>
</div>
</header>
</main>
<link rel="stylesheet" href="/static/css/resource.css" />
<style scoped>
.stack-name {
color: var(--text-accent);
}
</style>
{% endblock %}

View file

@ -11,6 +11,7 @@
<form action="./restart" method="POST"><button type="submit">Restart</button></form> <form action="./restart" method="POST"><button type="submit">Restart</button></form>
<form action="./stop" method="POST"><button type="submit">Stop</button></form> <form action="./stop" method="POST"><button type="submit">Stop</button></form>
<form action="./down" method="POST"><button type="submit">Down</button></form> <form action="./down" method="POST"><button type="submit">Down</button></form>
<form action="./delete" method="GET"><button type="submit">Delete</button></form>
</div> </div>
</header> </header>
<section class="container-list"> <section class="container-list">
@ -50,6 +51,7 @@
</main> </main>
<link rel="stylesheet" href="/static/css/editor.css" /> <link rel="stylesheet" href="/static/css/editor.css" />
<link rel="stylesheet" href="/static/css/resource.css" />
<style scoped> <style scoped>
main { main {
display: flex; display: flex;
@ -69,31 +71,17 @@
padding: 0 10px; padding: 0 10px;
&.exited { &.exited {
background-color: #E5484D; background-color: var(--danger-bright);
} }
&.running { &.running {
background-color: #46A758; background-color: var(--success-bright);
} }
} }
} }
.stack-name { .stack-name {
color: #E796F3; color: var(--text-accent);
}
.actions {
display: flex;
gap: 0.5rem;
& form {
display: flex;
min-width: 80px;
}
& button {
flex: 1;
}
} }
</style> </style>