Compare commits
No commits in common. "096d44ae5920e05c50403024a0792f1fb95f99e4" and "3f62e8302e1be83e56f2559ae1f53272c8f496ab" have entirely different histories.
096d44ae59
...
3f62e8302e
10 changed files with 141 additions and 248 deletions
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use git2::{ErrorCode, Index, IndexAddOption, Oid, Repository, Signature};
|
use git2::{ErrorCode, IndexAddOption, Repository, Signature};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
@ -40,6 +40,9 @@ 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)?;
|
||||||
|
@ -68,15 +71,24 @@ impl ThreadSafeRepository {
|
||||||
Repository::open(&self.path)
|
Repository::open(&self.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&self, repository: Repository, mut index: Index, message: &str) -> Result<Oid> {
|
pub fn commit_files(&self, paths: &[&Path], message: &str) -> Result<()> {
|
||||||
index.write()?;
|
let repository = self.repository()?;
|
||||||
|
|
||||||
|
// 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)?;
|
||||||
let oid = repository.commit(
|
repository.commit(
|
||||||
Some("HEAD"),
|
Some("HEAD"),
|
||||||
&signature,
|
&signature,
|
||||||
&signature,
|
&signature,
|
||||||
|
@ -86,32 +98,6 @@ 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,27 +186,6 @@ 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)
|
||||||
|
|
|
@ -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,
|
||||||
remove, write_compose, StackCommand,
|
write_compose, StackCommand,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -37,12 +37,6 @@ 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),
|
||||||
|
@ -145,24 +139,6 @@ 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 {
|
||||||
|
@ -191,8 +167,4 @@ 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),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
|
|
||||||
& .error {
|
& .error {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: var(--danger-bg);
|
background-color: #500F1C;
|
||||||
color: var(--danger-text);
|
color: #FF9592;
|
||||||
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: var(--table-border);
|
border: 3px solid #5958B1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
&.checked {
|
&.checked {
|
||||||
border-color: var(--success-bright);
|
border-color: #46A758;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.err {
|
&.err {
|
||||||
border-color: var(--danger-bright);
|
border-color: #E5484D;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,107 +0,0 @@
|
||||||
.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,11 +2,6 @@
|
||||||
--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;
|
||||||
|
@ -16,14 +11,6 @@
|
||||||
--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;
|
||||||
|
@ -77,6 +64,7 @@ nav {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
@ -104,7 +92,7 @@ button {
|
||||||
|
|
||||||
table.table {
|
table.table {
|
||||||
background-color: var(--bg-raised);
|
background-color: var(--bg-raised);
|
||||||
border: var(--table-border);
|
border: 3px solid #5958B1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
& th,
|
& th,
|
||||||
|
@ -117,7 +105,7 @@ table.table {
|
||||||
}
|
}
|
||||||
|
|
||||||
& thead {
|
& thead {
|
||||||
background-color: var(--table-bg);
|
background-color: #202248;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +116,7 @@ table.table {
|
||||||
|
|
||||||
input,
|
input,
|
||||||
textarea {
|
textarea {
|
||||||
border: 1px solid var(--table-border-color);
|
border: 1px solid #5958B1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: var(--bg-raised);
|
background-color: var(--bg-raised);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
</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;
|
||||||
|
@ -96,13 +95,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-name {
|
.container-name {
|
||||||
color: var(--text-accent);
|
color: #E796F3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
|
||||||
& .key {
|
& .key {
|
||||||
color: var(--text-accent);
|
color: #E796F3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +115,8 @@
|
||||||
|
|
||||||
& .error {
|
& .error {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
background-color: var(--danger-bg);
|
background-color: #3B1219;
|
||||||
color: var(--danger-text);
|
color: #FF9592;
|
||||||
}
|
}
|
||||||
|
|
||||||
& p {
|
& p {
|
||||||
|
@ -136,6 +135,81 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -170,6 +244,20 @@
|
||||||
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">
|
||||||
|
|
|
@ -158,11 +158,11 @@
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
&.exited {
|
&.exited {
|
||||||
background-color: var(--danger-bright);
|
background-color: #E5484D;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.running {
|
&.running {
|
||||||
background-color: var(--success-bright);
|
background-color: #46A758;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@
|
||||||
&.status {
|
&.status {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: var(--danger-bright);
|
background-color: #E5484D;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: #33B074;
|
background-color: #33B074;
|
||||||
|
@ -210,11 +210,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.running::before {
|
&.running::before {
|
||||||
color: var(--success-bright);
|
color: #46A758;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.stopped::before {
|
&.stopped::before {
|
||||||
color: var(--danger-bright);
|
color: #E5484D;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
{% 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,7 +11,6 @@
|
||||||
<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">
|
||||||
|
@ -51,7 +50,6 @@
|
||||||
</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;
|
||||||
|
@ -71,17 +69,31 @@
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
&.exited {
|
&.exited {
|
||||||
background-color: var(--danger-bright);
|
background-color: #E5484D;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.running {
|
&.running {
|
||||||
background-color: var(--success-bright);
|
background-color: #46A758;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-name {
|
.stack-name {
|
||||||
color: var(--text-accent);
|
color: #E796F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
& form {
|
||||||
|
display: flex;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
Reference in a new issue