Compare commits
2 commits
3f62e8302e
...
096d44ae59
Author | SHA1 | Date | |
---|---|---|---|
096d44ae59 | |||
ddb4ec4a5c |
10 changed files with 248 additions and 141 deletions
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use git2::{ErrorCode, IndexAddOption, Repository, Signature};
|
||||
use git2::{ErrorCode, Index, IndexAddOption, Oid, Repository, Signature};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::info;
|
||||
|
||||
|
@ -40,9 +40,6 @@ impl ThreadSafeRepository {
|
|||
index.add_all(["*/*.nix"].iter(), IndexAddOption::DEFAULT, None)?;
|
||||
let oid = index.write_tree()?;
|
||||
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()?;
|
||||
|
||||
let signature = Signature::now(&config.author_name, &config.author_email)?;
|
||||
|
@ -71,24 +68,15 @@ impl ThreadSafeRepository {
|
|||
Repository::open(&self.path)
|
||||
}
|
||||
|
||||
pub fn commit_files(&self, paths: &[&Path], message: &str) -> Result<()> {
|
||||
let repository = self.repository()?;
|
||||
fn commit(&self, repository: Repository, mut index: Index, message: &str) -> Result<Oid> {
|
||||
index.write()?;
|
||||
|
||||
// Commit file
|
||||
let mut index = repository.index()?;
|
||||
for path in paths {
|
||||
index.add_path(path)?;
|
||||
}
|
||||
let oid = index.write_tree()?;
|
||||
let tree = repository.find_tree(oid)?;
|
||||
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)?;
|
||||
repository.commit(
|
||||
let oid = repository.commit(
|
||||
Some("HEAD"),
|
||||
&signature,
|
||||
&signature,
|
||||
|
@ -98,6 +86,32 @@ impl ThreadSafeRepository {
|
|||
)?;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,6 +186,27 @@ pub async fn create_new(
|
|||
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<()> {
|
||||
// Check that it's a valid nix tree
|
||||
rnix::Root::parse(source)
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
container::ContainerInfo,
|
||||
stack::{
|
||||
check_compose, command, commit_compose, create_new, get_compose, get_containers,
|
||||
write_compose, StackCommand,
|
||||
remove, write_compose, StackCommand,
|
||||
},
|
||||
},
|
||||
AppState,
|
||||
|
@ -37,6 +37,12 @@ struct GetOneTemplate {
|
|||
#[template(path = "stack/new-form.html")]
|
||||
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 {
|
||||
let (file_contents_res, containers_res) = join!(
|
||||
get_compose(&state.stack_dir, &stack_name),
|
||||
|
@ -139,6 +145,24 @@ async fn check_stack_file(
|
|||
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 {
|
||||
($cmd: expr) => {
|
||||
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/down", post(stack_command!(StackCommand::Down)))
|
||||
.route("/:stack/edit", post(edit_stack))
|
||||
.route(
|
||||
"/:stack/delete",
|
||||
get(confirm_deletion_page).post(delete_stack),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
& .error {
|
||||
border-radius: 3px;
|
||||
background-color: #500F1C;
|
||||
color: #FF9592;
|
||||
background-color: var(--danger-bg);
|
||||
color: var(--danger-text);
|
||||
padding: 4px 8px;
|
||||
display: none;
|
||||
}
|
||||
|
@ -35,14 +35,14 @@
|
|||
|
||||
.ace_editor {
|
||||
min-height: 50vh;
|
||||
border: 3px solid #5958B1;
|
||||
border: var(--table-border);
|
||||
border-radius: 3px;
|
||||
|
||||
&.checked {
|
||||
border-color: #46A758;
|
||||
border-color: var(--success-bright);
|
||||
}
|
||||
|
||||
&.err {
|
||||
border-color: #E5484D;
|
||||
border-color: var(--danger-bright);
|
||||
}
|
||||
}
|
107
static/css/resource.css
Normal file
107
static/css/resource.css
Normal 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;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,11 @@
|
|||
--background: #13131E;
|
||||
--bg-raised: #171625;
|
||||
--text: #E0DFFE;
|
||||
--text-accent: #E796F3;
|
||||
|
||||
--table-border-color: #5958B1;
|
||||
--table-border: 3px solid var(--table-border-color);
|
||||
--table-bg: #202248;
|
||||
|
||||
--link: #FFC53D;
|
||||
--link-hover: #e28d0e;
|
||||
|
@ -11,6 +16,14 @@
|
|||
--button-border: #5B5BD6;
|
||||
--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);
|
||||
color: var(--text);
|
||||
font-family: Inter, sans-serif;
|
||||
|
@ -64,7 +77,6 @@ nav {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
main {
|
||||
padding: 10px;
|
||||
}
|
||||
|
@ -92,7 +104,7 @@ button {
|
|||
|
||||
table.table {
|
||||
background-color: var(--bg-raised);
|
||||
border: 3px solid #5958B1;
|
||||
border: var(--table-border);
|
||||
border-radius: 3px;
|
||||
|
||||
& th,
|
||||
|
@ -105,7 +117,7 @@ table.table {
|
|||
}
|
||||
|
||||
& thead {
|
||||
background-color: #202248;
|
||||
background-color: var(--table-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +128,7 @@ table.table {
|
|||
|
||||
input,
|
||||
textarea {
|
||||
border: 1px solid #5958B1;
|
||||
border: 1px solid var(--table-border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--bg-raised);
|
||||
color: var(--text);
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
</section>
|
||||
</main>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/resource.css" />
|
||||
<style scoped>
|
||||
a.stack-name {
|
||||
text-decoration: none;
|
||||
|
@ -95,13 +96,13 @@
|
|||
}
|
||||
|
||||
.container-name {
|
||||
color: #E796F3;
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.label {
|
||||
|
||||
& .key {
|
||||
color: #E796F3;
|
||||
color: var(--text-accent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,8 +116,8 @@
|
|||
|
||||
& .error {
|
||||
padding: 4px 8px;
|
||||
background-color: #3B1219;
|
||||
color: #FF9592;
|
||||
background-color: var(--danger-bg);
|
||||
color: var(--danger-text);
|
||||
}
|
||||
|
||||
& 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 {
|
||||
|
@ -244,20 +170,6 @@
|
|||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
& form {
|
||||
display: flex;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
& button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="module">
|
||||
|
|
|
@ -158,11 +158,11 @@
|
|||
padding: 0 10px;
|
||||
|
||||
&.exited {
|
||||
background-color: #E5484D;
|
||||
background-color: var(--danger-bright);
|
||||
}
|
||||
|
||||
&.running {
|
||||
background-color: #46A758;
|
||||
background-color: var(--success-bright);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@
|
|||
&.status {
|
||||
width: 6px;
|
||||
padding: 0;
|
||||
background-color: #E5484D;
|
||||
background-color: var(--danger-bright);
|
||||
|
||||
&.active {
|
||||
background-color: #33B074;
|
||||
|
@ -210,11 +210,11 @@
|
|||
}
|
||||
|
||||
&.running::before {
|
||||
color: #46A758;
|
||||
color: var(--success-bright);
|
||||
}
|
||||
|
||||
&.stopped::before {
|
||||
color: #E5484D;
|
||||
color: var(--danger-bright);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
25
templates/stack/delete-one.html
Normal file
25
templates/stack/delete-one.html
Normal 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 %}
|
|
@ -11,6 +11,7 @@
|
|||
<form action="./restart" method="POST"><button type="submit">Restart</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="./delete" method="GET"><button type="submit">Delete</button></form>
|
||||
</div>
|
||||
</header>
|
||||
<section class="container-list">
|
||||
|
@ -50,6 +51,7 @@
|
|||
</main>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/editor.css" />
|
||||
<link rel="stylesheet" href="/static/css/resource.css" />
|
||||
<style scoped>
|
||||
main {
|
||||
display: flex;
|
||||
|
@ -69,31 +71,17 @@
|
|||
padding: 0 10px;
|
||||
|
||||
&.exited {
|
||||
background-color: #E5484D;
|
||||
background-color: var(--danger-bright);
|
||||
}
|
||||
|
||||
&.running {
|
||||
background-color: #46A758;
|
||||
background-color: var(--success-bright);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stack-name {
|
||||
color: #E796F3;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
& form {
|
||||
display: flex;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
& button {
|
||||
flex: 1;
|
||||
}
|
||||
color: var(--text-accent);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
Reference in a new issue