forked from github-starred/komodo
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aa9513dd0 | ||
|
|
26b216b478 | ||
|
|
166299bb57 | ||
|
|
03c47eb3dc | ||
|
|
1fcb4ad085 | ||
|
|
f51af8fbe1 | ||
|
|
4a975e1b92 | ||
|
|
ba556e3284 | ||
|
|
299a326942 | ||
|
|
a5d4b9aefb | ||
|
|
40b820ae42 | ||
|
|
7028bf2996 | ||
|
|
75ebd0e6c0 | ||
|
|
426153df66 | ||
|
|
5bd423a6a6 | ||
|
|
c24131d383 | ||
|
|
9f54b6c26a | ||
|
|
ab8ae51ece | ||
|
|
ef2a83ff16 | ||
|
|
7872771aee | ||
|
|
b12cf858d8 | ||
|
|
38dba91c3a | ||
|
|
ea8136aa57 | ||
|
|
f956e12e28 | ||
|
|
207ea52b95 | ||
|
|
caf28d3a26 | ||
|
|
8fff45649d | ||
|
|
de5df70e11 | ||
|
|
3df010ac2a | ||
|
|
2d3beb708e | ||
|
|
1dc22d01c4 | ||
|
|
eb029d0408 | ||
|
|
f926932181 | ||
|
|
cc96d80c6a | ||
|
|
144b49495c | ||
|
|
de9354bdc7 | ||
|
|
38bfee84d7 | ||
|
|
ec33d9fb9e | ||
|
|
0a66937b1d | ||
|
|
43cc0c3bc1 | ||
|
|
c14b395c70 | ||
|
|
7b8529a7c6 | ||
|
|
547c089581 | ||
|
|
4fe5e461b3 | ||
|
|
edfb873f7c | ||
|
|
5ef5294c44 | ||
|
|
5d3c50e04f | ||
|
|
f10efbb5ba | ||
|
|
39ce98161b | ||
|
|
cff6e79eee | ||
|
|
dedf22ede8 | ||
|
|
6955b92a99 | ||
|
|
5c63eeab02 | ||
|
|
4c14a4ae20 | ||
|
|
29fd856a2d | ||
|
|
195bdbd94a | ||
|
|
298ccd945c | ||
|
|
436e4e79e9 |
Generated
+21
-54
@@ -32,7 +32,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
@@ -41,7 +41,6 @@ dependencies = [
|
||||
"logger",
|
||||
"monitor_client",
|
||||
"serde",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -923,7 +922,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
@@ -1299,6 +1298,10 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.8.0"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -1423,10 +1426,11 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
"formatting",
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
"tracing",
|
||||
@@ -1977,7 +1981,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -2046,7 +2050,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2056,7 +2060,6 @@ dependencies = [
|
||||
"monitor_client",
|
||||
"mungos",
|
||||
"serde",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2169,7 +2172,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2189,7 +2192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2221,7 +2224,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_core"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2234,6 +2237,7 @@ dependencies = [
|
||||
"derive_variants",
|
||||
"dotenv",
|
||||
"envy",
|
||||
"formatting",
|
||||
"futures",
|
||||
"git",
|
||||
"hex",
|
||||
@@ -2256,8 +2260,8 @@ dependencies = [
|
||||
"serror",
|
||||
"sha2",
|
||||
"slack_client_rs",
|
||||
"strum 0.26.2",
|
||||
"svi",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
@@ -2272,7 +2276,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2296,7 +2300,6 @@ dependencies = [
|
||||
"serror",
|
||||
"svi",
|
||||
"sysinfo",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
@@ -2608,7 +2611,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -3291,16 +3294,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -3310,18 +3303,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-tokio"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"libc",
|
||||
"signal-hook",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@@ -3570,22 +3551,9 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termination_signal"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16e2993a3469a2732b1c07df31a0227f2354500779c175f42af4c21693f1ad8f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
"signal-hook",
|
||||
"signal-hook-tokio",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenv",
|
||||
@@ -3822,9 +3790,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_pretty"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "100b9fea6caaa91d9e7837591cbda2958b6ff16318b13c8ba92254fdda13db47"
|
||||
checksum = "dd412f79539d72f97ac0ec9905b0178be1be4f255ddc2ae6f1ff3f7b234bb870"
|
||||
dependencies = [
|
||||
"ordered_hash_map",
|
||||
"serde",
|
||||
@@ -4170,12 +4138,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"logger",
|
||||
"monitor_client",
|
||||
"termination_signal",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.7.3"
|
||||
version = "1.8.0"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -15,8 +15,9 @@ monitor_client = { path = "client/core/rs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
monitor_client = "1.7.3"
|
||||
monitor_client = "1.8.0"
|
||||
periphery_client = { path = "client/periphery/rs" }
|
||||
formatting = { path = "lib/formatting" }
|
||||
command = { path = "lib/command" }
|
||||
logger = { path = "lib/logger" }
|
||||
git = { path = "lib/git" }
|
||||
@@ -28,13 +29,12 @@ slack = { version = "0.1.0", package = "slack_client_rs" }
|
||||
derive_default_builder = "0.1.8"
|
||||
derive_empty_traits = "0.1.0"
|
||||
merge_config_files = "0.1.5"
|
||||
termination_signal = "0.1.3"
|
||||
async_timing_util = "0.1.14"
|
||||
partial_derive2 = "0.4.3"
|
||||
derive_variants = "1.0.0"
|
||||
mongo_indexed = "0.3.0"
|
||||
resolver_api = "1.1.0"
|
||||
toml_pretty = "1.1.1"
|
||||
toml_pretty = "1.1.2"
|
||||
parse_csl = "0.1.0"
|
||||
mungos = "0.5.6"
|
||||
svi = "1.0.1"
|
||||
|
||||
@@ -13,8 +13,6 @@ repository.workspace = true
|
||||
# local
|
||||
monitor_client.workspace = true
|
||||
logger.workspace = true
|
||||
# mogh
|
||||
termination_signal.workspace = true
|
||||
# external
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -9,7 +9,6 @@ use monitor_client::entities::{
|
||||
alert::Alert, server::stats::SeverityLevel,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use termination_signal::tokio::immediate_term_handle;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Env {
|
||||
@@ -57,13 +56,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let term_signal = immediate_term_handle()?;
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
_ = term_signal => {},
|
||||
res = app => return res?,
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -80,15 +80,25 @@ pub fn get_updates(
|
||||
to_update.push(item);
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
"CREATE".green(),
|
||||
variable.name.bold().green(),
|
||||
"description".dimmed(),
|
||||
variable.description,
|
||||
"value".dimmed(),
|
||||
variable.value,
|
||||
);
|
||||
if variable.description.is_empty() {
|
||||
println!(
|
||||
"\n{}: variable: {}\n{}: {}",
|
||||
"CREATE".green(),
|
||||
variable.name.bold().green(),
|
||||
"value".dimmed(),
|
||||
variable.value,
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
"CREATE".green(),
|
||||
variable.name.bold().green(),
|
||||
"description".dimmed(),
|
||||
variable.description,
|
||||
"value".dimmed(),
|
||||
variable.value,
|
||||
);
|
||||
}
|
||||
to_create.push(variable)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,12 +17,12 @@ path = "src/main.rs"
|
||||
# local
|
||||
monitor_client = { workspace = true, features = ["mongo"] }
|
||||
periphery_client.workspace = true
|
||||
formatting.workspace = true
|
||||
logger.workspace = true
|
||||
git.workspace = true
|
||||
# mogh
|
||||
serror = { workspace = true, features = ["axum"] }
|
||||
merge_config_files.workspace = true
|
||||
termination_signal.workspace = true
|
||||
async_timing_util.workspace = true
|
||||
partial_derive2.workspace = true
|
||||
derive_variants.workspace = true
|
||||
@@ -53,6 +53,7 @@ base64.workspace = true
|
||||
tokio.workspace = true
|
||||
tower.workspace = true
|
||||
serde.workspace = true
|
||||
strum.workspace = true
|
||||
axum.workspace = true
|
||||
toml.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::muted;
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::{
|
||||
@@ -59,7 +60,7 @@ use crate::helpers::update::init_execution_update;
|
||||
use super::ExecuteRequest;
|
||||
|
||||
impl Resolve<RunBuild, (User, Update)> for State {
|
||||
#[instrument(name = "RunBuild", skip(self, user))]
|
||||
#[instrument(name = "RunBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunBuild { build }: RunBuild,
|
||||
@@ -102,10 +103,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
id = cancel_recv.recv() => id?
|
||||
};
|
||||
if incoming_build_id == build_id {
|
||||
info!("build cancel acknowledged");
|
||||
update.push_simple_log(
|
||||
"cancel acknowledged",
|
||||
"the build cancellation has been queud, it may still take some time",
|
||||
"the build cancellation has been queued, it may still take some time",
|
||||
);
|
||||
update.finalize();
|
||||
let id = update.id.clone();
|
||||
@@ -338,6 +338,7 @@ async fn handle_early_return(
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn validate_cancel_build(
|
||||
request: &ExecuteRequest,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -381,7 +382,7 @@ pub async fn validate_cancel_build(
|
||||
}
|
||||
|
||||
impl Resolve<CancelBuild, (User, Update)> for State {
|
||||
#[instrument(name = "CancelBuild", skip(self, user))]
|
||||
#[instrument(name = "CancelBuild", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CancelBuild { build }: CancelBuild,
|
||||
@@ -672,7 +673,18 @@ fn start_aws_builder_log(
|
||||
|
||||
let readable_sec_group_ids = security_group_ids.join(", ");
|
||||
|
||||
format!("instance id: {instance_id}\nip: {ip}\nami id: {ami_id}\ninstance type: {instance_type}\nvolume size: {volume_gb} GB\nsubnet id: {subnet_id}\nsecurity groups: {readable_sec_group_ids}\nassign public ip: {assign_public_ip}\nuse public ip: {use_public_ip}")
|
||||
[
|
||||
format!("{}: {instance_id}", muted("instance id")),
|
||||
format!("{}: {ip}", muted("ip")),
|
||||
format!("{}: {ami_id}", muted("ami id")),
|
||||
format!("{}: {instance_type}", muted("instance type")),
|
||||
format!("{}: {volume_gb} GB", muted("volume size")),
|
||||
format!("{}: {subnet_id}", muted("subnet id")),
|
||||
format!("{}: {readable_sec_group_ids}", muted("security groups")),
|
||||
format!("{}: {assign_public_ip}", muted("assign public ip")),
|
||||
format!("{}: {use_public_ip}", muted("use public ip")),
|
||||
]
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/// This will make sure that a build with non-none image registry has an account attached,
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
use crate::helpers::update::init_execution_update;
|
||||
|
||||
impl Resolve<Deploy, (User, Update)> for State {
|
||||
#[instrument(name = "Deploy", skip(self, user))]
|
||||
#[instrument(name = "Deploy", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
Deploy {
|
||||
@@ -203,7 +203,7 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<StartContainer, (User, Update)> for State {
|
||||
#[instrument(name = "StartContainer", skip(self, user))]
|
||||
#[instrument(name = "StartContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StartContainer { deployment }: StartContainer,
|
||||
@@ -263,7 +263,7 @@ impl Resolve<StartContainer, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<StopContainer, (User, Update)> for State {
|
||||
#[instrument(name = "StopContainer", skip(self, user))]
|
||||
#[instrument(name = "StopContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StopContainer {
|
||||
@@ -333,7 +333,7 @@ impl Resolve<StopContainer, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<StopAllContainers, (User, Update)> for State {
|
||||
#[instrument(name = "StopAllContainers", skip(self, user))]
|
||||
#[instrument(name = "StopAllContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
StopAllContainers { server }: StopAllContainers,
|
||||
@@ -419,7 +419,7 @@ impl Resolve<StopAllContainers, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<RemoveContainer, (User, Update)> for State {
|
||||
#[instrument(name = "RemoveContainer", skip(self, user))]
|
||||
#[instrument(name = "RemoveContainer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RemoveContainer {
|
||||
|
||||
@@ -121,7 +121,7 @@ async fn handler(
|
||||
Ok(Json(update))
|
||||
}
|
||||
|
||||
#[instrument(name = "ExecuteRequest", skip(user, update))]
|
||||
#[instrument(name = "ExecuteRequest", skip(user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: ExecuteRequest,
|
||||
@@ -149,7 +149,7 @@ async fn task(
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/execute request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/execute request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::execute::RunProcedure,
|
||||
entities::{
|
||||
@@ -19,7 +20,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<RunProcedure, (User, Update)> for State {
|
||||
#[instrument(name = "RunProcedure", skip(self, user))]
|
||||
#[instrument(name = "RunProcedure", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunProcedure { procedure }: RunProcedure,
|
||||
@@ -51,7 +52,11 @@ fn resolve_inner(
|
||||
// and will panic otherwise.
|
||||
update.push_simple_log(
|
||||
"execute_procedure",
|
||||
format!("executing procedure {}", procedure.name),
|
||||
format!(
|
||||
"{}: executing procedure '{}'",
|
||||
muted("INFO"),
|
||||
bold(&procedure.name)
|
||||
),
|
||||
);
|
||||
|
||||
// get the action state for the procedure (or insert default).
|
||||
@@ -75,12 +80,20 @@ fn resolve_inner(
|
||||
Ok(_) => {
|
||||
update.push_simple_log(
|
||||
"execution ok",
|
||||
"the procedure has completed with no errors",
|
||||
format!(
|
||||
"{}: the procedure has {} with no errors",
|
||||
muted("INFO"),
|
||||
colored("completed", Color::Green)
|
||||
),
|
||||
);
|
||||
}
|
||||
Err(e) => update.push_error_log(
|
||||
"execution error",
|
||||
serialize_error_pretty(&e),
|
||||
format!(
|
||||
"{}: {}",
|
||||
colored("ERROR", Color::Red),
|
||||
serialize_error_pretty(&e)
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
#[instrument(name = "CloneRepo", skip(self, user))]
|
||||
#[instrument(name = "CloneRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CloneRepo { repo }: CloneRepo,
|
||||
@@ -87,7 +87,7 @@ impl Resolve<CloneRepo, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<PullRepo, (User, Update)> for State {
|
||||
#[instrument(name = "PullRepo", skip(self, user))]
|
||||
#[instrument(name = "PullRepo", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PullRepo { repo }: PullRepo,
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<PruneContainers, (User, Update)> for State {
|
||||
#[instrument(name = "PruneContainers", skip(self, user))]
|
||||
#[instrument(name = "PruneContainers", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneContainers { server }: PruneContainers,
|
||||
@@ -71,7 +71,7 @@ impl Resolve<PruneContainers, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<PruneNetworks, (User, Update)> for State {
|
||||
#[instrument(name = "PruneNetworks", skip(self, user))]
|
||||
#[instrument(name = "PruneNetworks", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneNetworks { server }: PruneNetworks,
|
||||
@@ -122,7 +122,7 @@ impl Resolve<PruneNetworks, (User, Update)> for State {
|
||||
}
|
||||
|
||||
impl Resolve<PruneImages, (User, Update)> for State {
|
||||
#[instrument(name = "PruneImages", skip(self, user))]
|
||||
#[instrument(name = "PruneImages", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PruneImages { server }: PruneImages,
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<LaunchServer, (User, Update)> for State {
|
||||
#[instrument(name = "LaunchServer", skip(self, user))]
|
||||
#[instrument(name = "LaunchServer", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
LaunchServer {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::{colored, Color};
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::{execute::RunSync, write::RefreshResourceSyncPending},
|
||||
@@ -7,7 +8,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::{
|
||||
colored,
|
||||
deployment,
|
||||
resource::{
|
||||
get_updates_for_execution, AllResourcesById, ResourceSync,
|
||||
},
|
||||
@@ -38,6 +38,7 @@ use crate::{
|
||||
};
|
||||
|
||||
impl Resolve<RunSync, (User, Update)> for State {
|
||||
#[instrument(name = "RunSync", skip(self, user, update), fields(user_id = user.id, update_id = update.id))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
RunSync { sync }: RunSync,
|
||||
@@ -77,7 +78,7 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
) = get_updates_for_execution::<Deployment>(
|
||||
) = deployment::get_updates_for_execution(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
@@ -205,7 +206,10 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
{
|
||||
update.push_simple_log(
|
||||
"No Changes",
|
||||
format!("{}. exiting.", colored("nothing to do", "green")),
|
||||
format!(
|
||||
"{}. exiting.",
|
||||
colored("nothing to do", Color::Green)
|
||||
),
|
||||
);
|
||||
update.finalize();
|
||||
update_update(update.clone()).await?;
|
||||
@@ -302,15 +306,15 @@ impl Resolve<RunSync, (User, Update)> for State {
|
||||
);
|
||||
|
||||
// Dependant on server / build
|
||||
maybe_extend(
|
||||
&mut update.logs,
|
||||
Deployment::run_updates(
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
if let Some(res) = deployment::run_updates(
|
||||
deployments_to_create,
|
||||
deployments_to_update,
|
||||
deployments_to_delete,
|
||||
)
|
||||
.await
|
||||
{
|
||||
update.logs.extend(res);
|
||||
}
|
||||
|
||||
// Dependant on everything
|
||||
maybe_extend(
|
||||
|
||||
@@ -201,6 +201,7 @@ impl Resolve<GetBuildVersions, User> for State {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
limit,
|
||||
}: GetBuildVersions,
|
||||
user: User,
|
||||
) -> anyhow::Result<Vec<BuildVersionResponseItem>> {
|
||||
@@ -233,7 +234,10 @@ impl Resolve<GetBuildVersions, User> for State {
|
||||
let versions = find_collect(
|
||||
&db_client().await.updates,
|
||||
filter,
|
||||
FindOptions::builder().sort(doc! { "_id": -1 }).build(),
|
||||
FindOptions::builder()
|
||||
.sort(doc! { "_id": -1 })
|
||||
.limit(limit)
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.context("failed to pull versions from mongo")?
|
||||
|
||||
@@ -121,6 +121,7 @@ impl Resolve<SearchLog, User> for State {
|
||||
deployment,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
}: SearchLog,
|
||||
user: User,
|
||||
) -> anyhow::Result<Log> {
|
||||
@@ -143,6 +144,7 @@ impl Resolve<SearchLog, User> for State {
|
||||
name,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
})
|
||||
.await
|
||||
.context("failed at call to periphery")
|
||||
|
||||
@@ -172,17 +172,14 @@ pub fn router() -> Router {
|
||||
.layer(middleware::from_fn(auth_request))
|
||||
}
|
||||
|
||||
#[instrument(name = "ReadHandler", level = "debug", skip(user))]
|
||||
#[instrument(name = "ReadHandler", level = "debug", skip(user), fields(user_id = user.id))]
|
||||
async fn handler(
|
||||
Extension(user): Extension<User>,
|
||||
Json(request): Json<ReadRequest>,
|
||||
) -> serror::Result<(TypedHeader<ContentType>, String)> {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
debug!(
|
||||
"/read request {req_id} | user: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
debug!("/read request | user: {}", user.username);
|
||||
let res =
|
||||
State
|
||||
.resolve_request(request, user)
|
||||
@@ -194,7 +191,7 @@ async fn handler(
|
||||
resolver_api::Error::Inner(e) => e,
|
||||
});
|
||||
if let Err(e) = &res {
|
||||
warn!("/read request {req_id} error: {e:#}");
|
||||
debug!("/read request {req_id} error: {e:#}");
|
||||
}
|
||||
let elapsed = timer.elapsed();
|
||||
debug!("/read request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
@@ -118,13 +118,22 @@ impl Resolve<ExportAllResourcesToToml, User> for State {
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<ServerTemplate>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
ResourceQuery::builder().tags(tags.clone()).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::ServerTemplate(resource.id)),
|
||||
);
|
||||
targets.extend(
|
||||
resource::list_for_user::<ResourceSync>(
|
||||
ResourceQuery::builder().tags(tags).build(),
|
||||
&user,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|resource| ResourceTarget::ResourceSync(resource.id)),
|
||||
);
|
||||
|
||||
let user_groups = if user.admin {
|
||||
find_collect(&db_client().await.user_groups, None, None)
|
||||
@@ -533,6 +542,8 @@ fn convert_resource<R: MonitorResource>(
|
||||
.filter_map(|t| tag_names.get(t).cloned())
|
||||
.collect(),
|
||||
description: resource.description,
|
||||
deploy: false,
|
||||
after: Default::default(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
@@ -570,6 +581,25 @@ fn serialize_resources_toml(
|
||||
.context("deployment has no config?")?
|
||||
.as_object_mut()
|
||||
.context("config is not object?")?;
|
||||
if let Some(DeploymentImage::Build { version, .. }) =
|
||||
&deployment.config.image
|
||||
{
|
||||
let image = config
|
||||
.get_mut("image")
|
||||
.context("deployment has no image")?
|
||||
.get_mut("params")
|
||||
.context("deployment image has no params")?
|
||||
.as_object_mut()
|
||||
.context("deployment image params is not object")?;
|
||||
if version.is_none() {
|
||||
image.remove("version");
|
||||
} else {
|
||||
image.insert(
|
||||
"version".to_string(),
|
||||
Value::String(version.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(term_signal_labels) =
|
||||
&deployment.config.term_signal_labels
|
||||
{
|
||||
|
||||
@@ -149,16 +149,13 @@ async fn handler(
|
||||
Ok((TypedHeader(ContentType::json()), res??))
|
||||
}
|
||||
|
||||
#[instrument(name = "WriteRequest", skip(user))]
|
||||
#[instrument(name = "WriteRequest", skip(user), fields(user_id = user.id))]
|
||||
async fn task(
|
||||
req_id: Uuid,
|
||||
request: WriteRequest,
|
||||
user: User,
|
||||
) -> anyhow::Result<String> {
|
||||
info!(
|
||||
"/write request {req_id} | user: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
info!("/write request | user: {}", user.username);
|
||||
|
||||
let timer = Instant::now();
|
||||
|
||||
@@ -178,7 +175,7 @@ async fn task(
|
||||
}
|
||||
|
||||
let elapsed = timer.elapsed();
|
||||
info!("/write request {req_id} | resolve time: {elapsed:?}");
|
||||
debug!("/write request {req_id} | resolve time: {elapsed:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::Deployment,
|
||||
monitor_timestamp,
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
@@ -33,7 +32,10 @@ use serror::serialize_error_pretty;
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_id_to_tags,
|
||||
sync::resource::{get_updates_for_view, AllResourcesById},
|
||||
sync::{
|
||||
deployment,
|
||||
resource::{get_updates_for_view, AllResourcesById},
|
||||
},
|
||||
},
|
||||
resource,
|
||||
state::{db_client, State},
|
||||
@@ -127,7 +129,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
|
||||
)
|
||||
.await
|
||||
.context("failed to get server updates")?,
|
||||
deployment_updates: get_updates_for_view::<Deployment>(
|
||||
deployment_updates: deployment::get_updates_for_view(
|
||||
resources.deployments,
|
||||
sync.config.delete,
|
||||
&all_resources,
|
||||
|
||||
@@ -216,8 +216,8 @@ impl GithubOauthClient {
|
||||
#[derive(Deserialize)]
|
||||
pub struct AccessTokenResponse {
|
||||
pub access_token: String,
|
||||
pub scope: String,
|
||||
pub token_type: String,
|
||||
// pub scope: String,
|
||||
// pub token_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -225,5 +225,5 @@ pub struct GithubUserResponse {
|
||||
pub login: String,
|
||||
pub id: u128,
|
||||
pub avatar_url: String,
|
||||
pub email: Option<String>,
|
||||
// pub email: Option<String>,
|
||||
}
|
||||
|
||||
@@ -185,10 +185,10 @@ impl GoogleOauthClient {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AccessTokenResponse {
|
||||
pub access_token: String,
|
||||
// pub access_token: String,
|
||||
pub id_token: String,
|
||||
pub scope: String,
|
||||
pub token_type: String,
|
||||
// pub scope: String,
|
||||
// pub token_type: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod aws;
|
||||
|
||||
#[allow(unused)]
|
||||
pub mod hetzner;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::{anyhow, Context, Ok};
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
@@ -21,9 +22,16 @@ pub async fn execute_procedure(
|
||||
update: &Mutex<Update>,
|
||||
) -> anyhow::Result<()> {
|
||||
for stage in &procedure.config.stages {
|
||||
if !stage.enabled {
|
||||
continue;
|
||||
}
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!("executing stage: {}", stage.name),
|
||||
&format!(
|
||||
"{}: executing stage: '{}'",
|
||||
muted("INFO"),
|
||||
bold(&stage.name)
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let timer = Instant::now();
|
||||
@@ -41,16 +49,19 @@ pub async fn execute_procedure(
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed stage '{}' execution after {:?}",
|
||||
stage.name,
|
||||
"{}: failed stage '{}' execution after {:?}",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&stage.name),
|
||||
timer.elapsed(),
|
||||
)
|
||||
})?;
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished stage '{}' execution in {:?} ✅",
|
||||
stage.name,
|
||||
"{}: {} stage '{}' execution in {:?}",
|
||||
muted("INFO"),
|
||||
colored("finished", Color::Green),
|
||||
bold(&stage.name),
|
||||
timer.elapsed()
|
||||
),
|
||||
)
|
||||
@@ -69,9 +80,15 @@ async fn execute_stage(
|
||||
) -> anyhow::Result<()> {
|
||||
let futures = executions.into_iter().map(|execution| async move {
|
||||
let now = Instant::now();
|
||||
add_line_to_update(update, &format!("executing: {execution:?}"))
|
||||
.await;
|
||||
let fail_log = format!("failed on {execution:?}");
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!("{}: executing: {execution:?}", muted("INFO")),
|
||||
)
|
||||
.await;
|
||||
let fail_log = format!(
|
||||
"{}: failed on {execution:?}",
|
||||
colored("ERROR", Color::Red)
|
||||
);
|
||||
let res =
|
||||
execute_execution(execution.clone(), parent_id, parent_name)
|
||||
.await
|
||||
@@ -79,7 +96,9 @@ async fn execute_stage(
|
||||
add_line_to_update(
|
||||
update,
|
||||
&format!(
|
||||
"finished execution in {:?}: {execution:?}",
|
||||
"{}: {} execution in {:?}: {execution:?}",
|
||||
muted("INFO"),
|
||||
colored("finished", Color::Green),
|
||||
now.elapsed()
|
||||
),
|
||||
)
|
||||
@@ -263,8 +282,9 @@ async fn execute_execution(
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"execution not successful. see update {}",
|
||||
update.id
|
||||
"{}: execution not successful. see update '{}'",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&update.id),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,858 @@
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::{execute::Deploy, read::GetBuildVersions},
|
||||
entities::{
|
||||
deployment::{
|
||||
Deployment, DeploymentConfig, DeploymentImage, DeploymentState,
|
||||
PartialDeploymentConfig,
|
||||
},
|
||||
sync::SyncUpdate,
|
||||
tag::Tag,
|
||||
toml::ResourceToml,
|
||||
update::{Log, ResourceTarget},
|
||||
user::sync_user,
|
||||
},
|
||||
};
|
||||
use mungos::find::find_collect;
|
||||
use partial_derive2::{Diff, FieldDiff, MaybeNone, PartialDiff};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
api::execute::ExecuteRequest,
|
||||
helpers::update::init_execution_update,
|
||||
resource::MonitorResource,
|
||||
state::{deployment_status_cache, State},
|
||||
};
|
||||
|
||||
use super::resource::{
|
||||
run_update_description, run_update_tags, AllResourcesById,
|
||||
ResourceSync,
|
||||
};
|
||||
|
||||
pub type ToUpdate = Vec<ToUpdateItem>;
|
||||
pub type ToCreate = Vec<ResourceToml<PartialDeploymentConfig>>;
|
||||
/// Vec of resource names
|
||||
pub type ToDelete = Vec<String>;
|
||||
|
||||
type UpdatesResult = (ToCreate, ToUpdate, ToDelete);
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub id: String,
|
||||
pub resource: ResourceToml<PartialDeploymentConfig>,
|
||||
pub update_description: bool,
|
||||
pub update_tags: bool,
|
||||
pub deploy: bool,
|
||||
}
|
||||
|
||||
/// Turns all the diffs into a readable string
|
||||
pub async fn get_updates_for_view(
|
||||
resources: Vec<ResourceToml<PartialDeploymentConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<Option<SyncUpdate>> {
|
||||
let map = find_collect(Deployment::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get deployments from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut update = SyncUpdate {
|
||||
log: format!("{} Updates", Deployment::resource_type()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut to_delete = Vec::<String>::new();
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
update.to_delete += 1;
|
||||
to_delete.push(resource.name.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_deploy_cache = HashMap::<String, bool>::new();
|
||||
let mut to_deploy_build_cache = HashMap::<String, String>::new();
|
||||
|
||||
for mut resource in resources.clone() {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (to_deploy, state, reason) = extract_to_deploy_and_state(
|
||||
all_resources,
|
||||
&map,
|
||||
&resources,
|
||||
resource.name.clone(),
|
||||
&mut to_deploy_cache,
|
||||
&mut to_deploy_build_cache,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& !to_deploy
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
if state == DeploymentState::Unknown {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\nDeployment sync actions could not be computed due to Unknown deployment state\n-------------------",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
update.to_update += 1;
|
||||
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", Color::Blue),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
if resource.description != original.description {
|
||||
lines.push(format!(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&resource.description, Color::Green)
|
||||
));
|
||||
}
|
||||
if resource.tags != original_tags {
|
||||
let from =
|
||||
colored(&format!("{:?}", original_tags), Color::Red);
|
||||
let to =
|
||||
colored(&format!("{:?}", resource.tags), Color::Green);
|
||||
lines.push(format!(
|
||||
"{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
muted("to"),
|
||||
));
|
||||
}
|
||||
lines.extend(diff.iter_field_diffs().map(
|
||||
|FieldDiff { field, from, to }| {
|
||||
format!(
|
||||
"{}: '{field}'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(from, Color::Red),
|
||||
muted("to"),
|
||||
colored(to, Color::Green)
|
||||
)
|
||||
},
|
||||
));
|
||||
if state == DeploymentState::Unknown {
|
||||
lines.push(format!(
|
||||
"{}: Deployment sync actions {} due to Unknown deployment state",
|
||||
colored("ERROR", Color::Red),
|
||||
bold("could not be computed")
|
||||
));
|
||||
} else if to_deploy {
|
||||
let mut line = if state == DeploymentState::Running {
|
||||
format!(
|
||||
"{}: {reason}, {}",
|
||||
muted("deploy"),
|
||||
bold("sync will trigger deploy")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}: deployment is currently in {} state, {}",
|
||||
muted("deploy"),
|
||||
colored(&state.to_string(), Color::Red),
|
||||
bold("sync will trigger deploy")
|
||||
)
|
||||
};
|
||||
if !resource.after.is_empty() {
|
||||
line.push_str(&format!(
|
||||
"\n{}: {:?}",
|
||||
muted("deploy after"),
|
||||
resource.after
|
||||
));
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
update.log.push('\n');
|
||||
update.log.push_str(&lines.join("\n-------------------\n"));
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
let mut lines = vec![
|
||||
format!(
|
||||
"{}: {}",
|
||||
muted("description"),
|
||||
resource.description,
|
||||
),
|
||||
format!("{}: {:?}", muted("tags"), resource.tags,),
|
||||
format!(
|
||||
"{}: {}",
|
||||
muted("config"),
|
||||
serde_json::to_string_pretty(&resource.config)
|
||||
.context("failed to serialize config to json")?
|
||||
),
|
||||
];
|
||||
if resource.deploy {
|
||||
lines.push(format!(
|
||||
"{}: {}",
|
||||
muted("will deploy"),
|
||||
colored("true", Color::Green)
|
||||
));
|
||||
if !resource.after.is_empty() {
|
||||
lines.push(format!(
|
||||
"{}: {:?}",
|
||||
muted("deploy after"),
|
||||
resource.after
|
||||
));
|
||||
}
|
||||
}
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: {}\n{}",
|
||||
colored("CREATE", Color::Green),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource.name),
|
||||
lines.join("\n")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
|
||||
let any_change = update.to_create > 0
|
||||
|| update.to_update > 0
|
||||
|| update.to_delete > 0;
|
||||
|
||||
Ok(any_change.then_some(update))
|
||||
}
|
||||
|
||||
/// Gets all the resources to update. For use in sync execution.
|
||||
pub async fn get_updates_for_execution(
|
||||
resources: Vec<ResourceToml<PartialDeploymentConfig>>,
|
||||
delete: bool,
|
||||
all_resources: &AllResourcesById,
|
||||
id_to_tags: &HashMap<String, Tag>,
|
||||
) -> anyhow::Result<UpdatesResult> {
|
||||
let map = find_collect(Deployment::coll().await, None, None)
|
||||
.await
|
||||
.context("failed to get deployments from db")?
|
||||
.into_iter()
|
||||
.map(|r| (r.name.clone(), r))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut to_create = ToCreate::new();
|
||||
let mut to_update = ToUpdate::new();
|
||||
let mut to_delete = ToDelete::new();
|
||||
|
||||
if delete {
|
||||
for resource in map.values() {
|
||||
if !resources.iter().any(|r| r.name == resource.name) {
|
||||
to_delete.push(resource.name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_deploy_cache = HashMap::<String, bool>::new();
|
||||
let mut to_deploy_build_cache = HashMap::<String, String>::new();
|
||||
|
||||
for mut resource in resources.clone() {
|
||||
match map.get(&resource.name) {
|
||||
Some(original) => {
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = resource.config.into();
|
||||
resource.config = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut resource.config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
resource.config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let original_tags = original
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|id| id_to_tags.get(id).map(|t| t.name.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (to_deploy, _state, _reason) =
|
||||
extract_to_deploy_and_state(
|
||||
all_resources,
|
||||
&map,
|
||||
&resources,
|
||||
resource.name.clone(),
|
||||
&mut to_deploy_cache,
|
||||
&mut to_deploy_build_cache,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Only proceed if there are any fields to update,
|
||||
// or a change to tags / description
|
||||
if diff.is_none()
|
||||
&& !to_deploy
|
||||
&& resource.description == original.description
|
||||
&& resource.tags == original_tags
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Minimizes updates through diffing.
|
||||
resource.config = diff.into();
|
||||
|
||||
let update = ToUpdateItem {
|
||||
id: original.id.clone(),
|
||||
update_description: resource.description
|
||||
!= original.description,
|
||||
update_tags: resource.tags != original_tags,
|
||||
resource,
|
||||
deploy: to_deploy,
|
||||
};
|
||||
|
||||
to_update.push(update);
|
||||
}
|
||||
None => to_create.push(resource),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((to_create, to_update, to_delete))
|
||||
}
|
||||
|
||||
type Res<'a> = std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<(bool, DeploymentState, String)>,
|
||||
> + Send
|
||||
+ 'a,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn extract_to_deploy_and_state<'a>(
|
||||
all_resources: &'a AllResourcesById,
|
||||
map: &'a HashMap<String, Deployment>,
|
||||
resources: &'a [ResourceToml<PartialDeploymentConfig>],
|
||||
name: String,
|
||||
// name to 'to_deploy'
|
||||
cache: &'a mut HashMap<String, bool>,
|
||||
// build id to latest built version string
|
||||
build_cache: &'a mut HashMap<String, String>,
|
||||
) -> Res<'a> {
|
||||
Box::pin(async move {
|
||||
let mut reason = String::new();
|
||||
let Some(deployment) = resources.iter().find(|r| r.name == name)
|
||||
else {
|
||||
// this case should be unreachable, the names come off of a loop over resources
|
||||
cache.insert(name, false);
|
||||
return Ok((false, DeploymentState::Unknown, reason));
|
||||
};
|
||||
if deployment.deploy {
|
||||
let Some(original) = map.get(&name) else {
|
||||
// not created, definitely deploy
|
||||
cache.insert(name, true);
|
||||
// Don't need reason here, will be populated automatically
|
||||
return Ok((true, DeploymentState::NotDeployed, reason));
|
||||
};
|
||||
|
||||
// First merge toml resource config (partial) onto default resource config.
|
||||
// Makes sure things that aren't defined in toml (come through as None) actually get removed.
|
||||
let config: DeploymentConfig = deployment.config.clone().into();
|
||||
let mut config: PartialDeploymentConfig = config.into();
|
||||
|
||||
Deployment::validate_partial_config(&mut config);
|
||||
|
||||
let mut diff = Deployment::get_diff(
|
||||
original.config.clone(),
|
||||
config,
|
||||
all_resources,
|
||||
)?;
|
||||
|
||||
Deployment::validate_diff(&mut diff);
|
||||
|
||||
let status = &deployment_status_cache()
|
||||
.get_or_insert_default(&original.id)
|
||||
.await
|
||||
.curr;
|
||||
let state = status.state;
|
||||
|
||||
let mut to_deploy = match state {
|
||||
DeploymentState::Unknown => false,
|
||||
DeploymentState::Running => {
|
||||
// Needs to only check config fields that affect docker run
|
||||
let changed = diff.server_id.is_some()
|
||||
|| diff.image.is_some()
|
||||
|| diff.image_registry.is_some()
|
||||
|| diff.skip_secret_interp.is_some()
|
||||
|| diff.network.is_some()
|
||||
|| diff.restart.is_some()
|
||||
|| diff.command.is_some()
|
||||
|| diff.extra_args.is_some()
|
||||
|| diff.ports.is_some()
|
||||
|| diff.volumes.is_some()
|
||||
|| diff.environment.is_some()
|
||||
|| diff.labels.is_some();
|
||||
if changed {
|
||||
reason = String::from("deployment config has changed")
|
||||
}
|
||||
changed
|
||||
}
|
||||
// All other cases will require Deploy to enter Running state.
|
||||
// Don't need reason here as this case is handled outside, using returned state.
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Check if build attached, version latest, and there is a new build.
|
||||
if !to_deploy {
|
||||
// only need to check original, if diff.image was Some, to_deploy would be true.
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.config.image
|
||||
{
|
||||
// check if version is none, ie use latest build
|
||||
if version.is_none() {
|
||||
let deployed_version = status
|
||||
.container
|
||||
.as_ref()
|
||||
.and_then(|c| c.image.split(':').last())
|
||||
.unwrap_or("0.0.0");
|
||||
match build_cache.get(build_id) {
|
||||
Some(version) if deployed_version != version => {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"attached build has new version ({version})"
|
||||
);
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let Some(version) = State
|
||||
.resolve(
|
||||
GetBuildVersions {
|
||||
build: build_id.to_string(),
|
||||
limit: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
sync_user().to_owned(),
|
||||
)
|
||||
.await
|
||||
.context("failed to get build versions")?
|
||||
.pop()
|
||||
else {
|
||||
// this case shouldn't ever happen, how would deployment be deployed if build was never built?
|
||||
return Ok((
|
||||
false,
|
||||
DeploymentState::NotDeployed,
|
||||
reason,
|
||||
));
|
||||
};
|
||||
let version = version.version.to_string();
|
||||
build_cache
|
||||
.insert(build_id.to_string(), version.clone());
|
||||
if deployed_version != version {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"attached build has new version ({version})"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still need to check 'after' if they need deploy
|
||||
if !to_deploy {
|
||||
for name in &deployment.after {
|
||||
match cache.get(name) {
|
||||
Some(will_deploy) if *will_deploy => {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"parent dependency '{}' is deploying",
|
||||
bold(name)
|
||||
);
|
||||
break;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
let (will_deploy, _, _) = extract_to_deploy_and_state(
|
||||
all_resources,
|
||||
map,
|
||||
resources,
|
||||
name.to_string(),
|
||||
cache,
|
||||
build_cache,
|
||||
)
|
||||
.await?;
|
||||
if will_deploy {
|
||||
to_deploy = true;
|
||||
reason = format!(
|
||||
"parent dependency '{}' is deploying",
|
||||
bold(name)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.insert(name, to_deploy);
|
||||
Ok((to_deploy, state, reason))
|
||||
} else {
|
||||
// The state in this case doesn't matter and won't be read (as long as it isn't 'Unknown' which will log in all cases)
|
||||
cache.insert(name, false);
|
||||
Ok((false, DeploymentState::NotDeployed, reason))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run_updates(
|
||||
to_create: ToCreate,
|
||||
to_update: ToUpdate,
|
||||
to_delete: ToDelete,
|
||||
) -> Option<Vec<Log>> {
|
||||
if to_create.is_empty()
|
||||
&& to_update.is_empty()
|
||||
&& to_delete.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut has_error = false;
|
||||
let mut log = String::new();
|
||||
|
||||
// Collect all the deployment names that need to be deployed
|
||||
// and their 'after' dependencies
|
||||
let mut to_deploy = Vec::<(String, Vec<String>)>::new();
|
||||
|
||||
for resource in to_create {
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
let id = match crate::resource::create::<Deployment>(
|
||||
&resource.name,
|
||||
resource.config,
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resource) => resource.id,
|
||||
Err(e) => {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
run_update_tags::<Deployment>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
run_update_description::<Deployment>(
|
||||
id,
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", Color::Green),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
if resource.deploy {
|
||||
to_deploy.push((resource.name, resource.after));
|
||||
}
|
||||
}
|
||||
|
||||
for ToUpdateItem {
|
||||
id,
|
||||
resource,
|
||||
update_description,
|
||||
update_tags,
|
||||
deploy,
|
||||
} in to_update
|
||||
{
|
||||
// Update resource
|
||||
let name = resource.name.clone();
|
||||
let tags = resource.tags.clone();
|
||||
let description = resource.description.clone();
|
||||
|
||||
if update_description {
|
||||
run_update_description::<Deployment>(
|
||||
id.clone(),
|
||||
&name,
|
||||
description,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if update_tags {
|
||||
run_update_tags::<Deployment>(
|
||||
id.clone(),
|
||||
&name,
|
||||
tags,
|
||||
&mut log,
|
||||
&mut has_error,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let mut config_update_error = false;
|
||||
if !resource.config.is_none() {
|
||||
if let Err(e) = crate::resource::update::<Deployment>(
|
||||
&id,
|
||||
resource.config,
|
||||
sync_user(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
config_update_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update config on {} '{}' | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' configuration",
|
||||
muted("INFO"),
|
||||
colored("updated", Color::Blue),
|
||||
Deployment::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !config_update_error && deploy {
|
||||
to_deploy.push((resource.name, resource.after));
|
||||
}
|
||||
}
|
||||
|
||||
for resource in to_delete {
|
||||
if let Err(e) =
|
||||
crate::resource::delete::<Deployment>(&resource, sync_user())
|
||||
.await
|
||||
{
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource),
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", Color::Red),
|
||||
Deployment::resource_type(),
|
||||
bold(&resource)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut logs = Vec::with_capacity(1);
|
||||
|
||||
let stage = format!("Update {}s", Deployment::resource_type());
|
||||
if has_error {
|
||||
let log = format!(
|
||||
"running updates on {}s{log}",
|
||||
Deployment::resource_type()
|
||||
);
|
||||
logs.push(Log::error(&stage, log));
|
||||
return Some(logs);
|
||||
} else if !log.is_empty() {
|
||||
let log = format!(
|
||||
"running updates on {}s{log}",
|
||||
Deployment::resource_type()
|
||||
);
|
||||
logs.push(Log::simple(&stage, log));
|
||||
}
|
||||
|
||||
if to_deploy.is_empty() {
|
||||
return Some(logs);
|
||||
}
|
||||
|
||||
let mut log = format!(
|
||||
"{}: running executions to sync deployment state",
|
||||
muted("INFO")
|
||||
);
|
||||
let mut round = 1;
|
||||
|
||||
while !to_deploy.is_empty() {
|
||||
// Collect all waiting deployments without waiting dependencies.
|
||||
let good_to_deploy = to_deploy
|
||||
.iter()
|
||||
.filter(|(_, after)| {
|
||||
to_deploy.iter().all(|(name, _)| !after.contains(name))
|
||||
})
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Deploy the ones ready for deployment
|
||||
let res =
|
||||
join_all(good_to_deploy.iter().map(|name| async move {
|
||||
let res = async {
|
||||
let req = ExecuteRequest::Deploy(Deploy {
|
||||
deployment: name.to_string(),
|
||||
stop_signal: None,
|
||||
stop_time: None,
|
||||
});
|
||||
let user = sync_user();
|
||||
let update = init_execution_update(&req, user).await?;
|
||||
let ExecuteRequest::Deploy(req) = req else {
|
||||
unreachable!()
|
||||
};
|
||||
State.resolve(req, (user.to_owned(), update)).await
|
||||
}
|
||||
.await;
|
||||
(name, res)
|
||||
}))
|
||||
.await;
|
||||
|
||||
// Log results of deploy
|
||||
for (name, res) in res {
|
||||
if let Err(e) = res {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to deploy '{}' in round {} | {e:#}",
|
||||
colored("ERROR", Color::Red),
|
||||
bold(name),
|
||||
bold(round)
|
||||
));
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: deployed '{}' in round {}",
|
||||
muted("INFO"),
|
||||
bold(name),
|
||||
bold(round)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Early exit if any deploy has errors
|
||||
if has_error {
|
||||
log.push_str(&format!(
|
||||
"\n{}: exited in round {} {}",
|
||||
muted("INFO"),
|
||||
bold(round),
|
||||
colored("with errors", Color::Red)
|
||||
));
|
||||
logs.push(Log::error("Sync Deployment State", log));
|
||||
return Some(logs);
|
||||
}
|
||||
|
||||
// Remove the deployed ones from 'to_deploy'
|
||||
to_deploy.retain(|(name, _)| !good_to_deploy.contains(name));
|
||||
|
||||
// If there must be another round, these are dependent on the first round.
|
||||
// Sleep for 1s to allow for first round to startup
|
||||
if !to_deploy.is_empty() {
|
||||
// Increment the round
|
||||
round += 1;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
log.push_str(&format!(
|
||||
"\n{}: finished after {} round{}",
|
||||
muted("INFO"),
|
||||
bold(round),
|
||||
(round > 1).then_some("s").unwrap_or_default()
|
||||
));
|
||||
|
||||
logs.push(Log::simple("Sync Deployment State", log));
|
||||
|
||||
Some(logs)
|
||||
}
|
||||
|
||||
impl ResourceSync for Deployment {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Deployment(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace the server id with name
|
||||
original.server_id = resources
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// need to replace the build id with name
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.image
|
||||
{
|
||||
original.image = DeploymentImage::Build {
|
||||
build_id: resources
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: version.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use formatting::{colored, muted, Color};
|
||||
use monitor_client::entities::{toml::ResourcesToml, update::Log};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use super::{colored, muted};
|
||||
|
||||
pub fn read_resources(
|
||||
path: &Path,
|
||||
) -> anyhow::Result<(ResourcesToml, Log)> {
|
||||
let mut res = ResourcesToml::default();
|
||||
let mut log = format!("reading resources from {path:?}");
|
||||
read_resources_recursive(path, &mut res, &mut log)?;
|
||||
let mut log =
|
||||
format!("{}: reading resources from {path:?}", muted("INFO"));
|
||||
read_resources_recursive(path, &mut res, &mut log).with_context(
|
||||
|| format!("failed to read resources from {path:?}"),
|
||||
)?;
|
||||
Ok((res, Log::simple("read remote resources", log)))
|
||||
}
|
||||
|
||||
@@ -30,20 +32,15 @@ fn read_resources_recursive(
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let more = match parse_toml_file::<ResourcesToml>(path) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
warn!("failed to parse {:?}. skipping file | {e:#}", path);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let more = parse_toml_file::<ResourcesToml>(path)
|
||||
.context("failed to parse resource file")?;
|
||||
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: {} from {}",
|
||||
muted("INFO"),
|
||||
colored("adding resources", "green"),
|
||||
colored(&path.display().to_string(), "blue")
|
||||
colored("adding resources", Color::Green),
|
||||
colored(path.display(), Color::Blue)
|
||||
));
|
||||
|
||||
resources.servers.extend(more.servers);
|
||||
@@ -63,15 +60,9 @@ fn read_resources_recursive(
|
||||
.context("failed to read directory contents")?;
|
||||
for entry in directory.into_iter().flatten() {
|
||||
let path = entry.path();
|
||||
if let Err(e) = read_resources_recursive(&path, resources, log)
|
||||
{
|
||||
log.push('\n');
|
||||
log.push_str(&format!(
|
||||
"{}: failed to read additional resources from {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored(&path.display().to_string(), "blue")
|
||||
));
|
||||
}
|
||||
read_resources_recursive(&path, resources, log).with_context(
|
||||
|| format!("failed to read resources from {path:?}"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -84,5 +75,8 @@ fn parse_toml_file<T: DeserializeOwned>(
|
||||
) -> anyhow::Result<T> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.context("failed to read file contents")?;
|
||||
toml::from_str(&contents).context("failed to parse toml contents")
|
||||
toml::from_str(&contents)
|
||||
// the error without this comes through with multiple lines (\n) and looks bad
|
||||
.map_err(|e| anyhow!("{e:#}"))
|
||||
.context("failed to parse toml contents")
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
pub mod deployment;
|
||||
pub mod remote;
|
||||
pub mod resource;
|
||||
pub mod user_groups;
|
||||
@@ -41,15 +42,3 @@ pub fn spawn_sync_refresh_loop() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn muted(content: &str) -> String {
|
||||
format!("<span class=\"text-muted-foreground\">{content}</span>")
|
||||
}
|
||||
|
||||
fn bold(content: &str) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn colored(content: &str, color: &str) -> String {
|
||||
format!("<span class=\"text-{color}-500\">{content}</span>")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::write::{UpdateDescription, UpdateTagsOnResource},
|
||||
entities::{
|
||||
@@ -26,8 +27,6 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::{resource::MonitorResource, state::State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub type ToUpdate<T> = Vec<ToUpdateItem<T>>;
|
||||
pub type ToCreate<T> = Vec<ResourceToml<T>>;
|
||||
/// Vec of resource names
|
||||
@@ -93,7 +92,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -119,7 +118,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -170,7 +169,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update config on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
@@ -178,7 +177,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' configuration",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -193,7 +192,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&resource),
|
||||
))
|
||||
@@ -201,7 +200,7 @@ pub trait ResourceSync: MonitorResource + Sized {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&resource)
|
||||
));
|
||||
@@ -283,7 +282,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
colored("UPDATE", Color::Blue),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name)
|
||||
));
|
||||
@@ -294,14 +293,16 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&resource.description, "green")
|
||||
colored(&resource.description, Color::Green)
|
||||
));
|
||||
}
|
||||
if resource.tags != original_tags {
|
||||
let from = colored(&format!("{:?}", original_tags), "red");
|
||||
let to = colored(&format!("{:?}", resource.tags), "green");
|
||||
let from =
|
||||
colored(&format!("{:?}", original_tags), Color::Red);
|
||||
let to =
|
||||
colored(&format!("{:?}", resource.tags), Color::Green);
|
||||
lines.push(format!(
|
||||
"{}: 'tags'\n{}: {from}\n{}: {to}",
|
||||
muted("field"),
|
||||
@@ -315,9 +316,9 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
"{}: '{field}'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&from, "red"),
|
||||
colored(from, Color::Red),
|
||||
muted("to"),
|
||||
colored(&to, "green")
|
||||
colored(to, Color::Green)
|
||||
)
|
||||
},
|
||||
));
|
||||
@@ -328,7 +329,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
colored("CREATE", Color::Green),
|
||||
Resource::resource_type(),
|
||||
bold(&resource.name),
|
||||
muted("description"),
|
||||
@@ -346,7 +347,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
|
||||
for name in to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: {}: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
colored("DELETE", Color::Red),
|
||||
Resource::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -459,7 +460,7 @@ pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update tags on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
@@ -467,7 +468,7 @@ pub async fn run_update_tags<Resource: ResourceSync>(
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' tags",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
Resource::resource_type(),
|
||||
bold(name)
|
||||
));
|
||||
@@ -494,7 +495,7 @@ pub async fn run_update_description<Resource: ResourceSync>(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update description on {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Resource::resource_type(),
|
||||
bold(name),
|
||||
))
|
||||
@@ -502,7 +503,7 @@ pub async fn run_update_description<Resource: ResourceSync>(
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
Resource::resource_type(),
|
||||
bold(name)
|
||||
));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::execute::Execution,
|
||||
entities::{
|
||||
@@ -5,7 +6,6 @@ use monitor_client::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::{Builder, BuilderConfig},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::Server,
|
||||
@@ -17,12 +17,9 @@ use monitor_client::{
|
||||
use partial_derive2::{MaybeNone, PartialDiff};
|
||||
|
||||
use crate::{
|
||||
helpers::sync::{
|
||||
bold, colored, muted,
|
||||
resource::{
|
||||
run_update_description, run_update_tags, ResourceSync,
|
||||
ToUpdateItem,
|
||||
},
|
||||
helpers::sync::resource::{
|
||||
run_update_description, run_update_tags, ResourceSync,
|
||||
ToUpdateItem,
|
||||
},
|
||||
resource::MonitorResource,
|
||||
};
|
||||
@@ -73,41 +70,6 @@ impl ResourceSync for Build {
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Deployment {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Deployment(id)
|
||||
}
|
||||
|
||||
fn get_diff(
|
||||
mut original: Self::Config,
|
||||
update: Self::PartialConfig,
|
||||
resources: &AllResourcesById,
|
||||
) -> anyhow::Result<Self::ConfigDiff> {
|
||||
// need to replace the server id with name
|
||||
original.server_id = resources
|
||||
.servers
|
||||
.get(&original.server_id)
|
||||
.map(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// need to replace the build id with name
|
||||
if let DeploymentImage::Build { build_id, version } =
|
||||
&original.image
|
||||
{
|
||||
original.image = DeploymentImage::Build {
|
||||
build_id: resources
|
||||
.builds
|
||||
.get(build_id)
|
||||
.map(|b| b.name.clone())
|
||||
.unwrap_or_default(),
|
||||
version: version.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(original.partial_diff(update))
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSync for Repo {
|
||||
fn resource_target(id: String) -> ResourceTarget {
|
||||
ResourceTarget::Repo(id)
|
||||
@@ -329,7 +291,7 @@ impl ResourceSync for Procedure {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"{}: failed to delete {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name),
|
||||
))
|
||||
@@ -337,7 +299,7 @@ impl ResourceSync for Procedure {
|
||||
log.push_str(&format!(
|
||||
"{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -398,7 +360,7 @@ impl ResourceSync for Procedure {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -437,7 +399,7 @@ impl ResourceSync for Procedure {
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create {} '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
@@ -464,7 +426,7 @@ impl ResourceSync for Procedure {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} {} '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
Self::resource_type(),
|
||||
bold(&name)
|
||||
));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::{
|
||||
read::ListUserTargetPermissions,
|
||||
@@ -22,7 +23,7 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted, resource::AllResourcesById};
|
||||
use super::resource::AllResourcesById;
|
||||
|
||||
pub struct UpdateItem {
|
||||
user_group: UserGroupToml,
|
||||
@@ -77,8 +78,8 @@ pub async fn get_updates_for_view(
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: {}\n{}: {:?}\n{}: {:?}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&user_group.name, "green"),
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&user_group.name, Color::Green),
|
||||
muted("users"),
|
||||
user_group.users,
|
||||
muted("permissions"),
|
||||
@@ -196,7 +197,7 @@ pub async fn get_updates_for_view(
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
colored("UPDATE", Color::Blue),
|
||||
bold(&user_group.name),
|
||||
));
|
||||
let mut lines = Vec::<String>::new();
|
||||
@@ -210,7 +211,7 @@ pub async fn get_updates_for_view(
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
colored(&adding.join(", "), Color::Green)
|
||||
};
|
||||
let removing = original_users
|
||||
.iter()
|
||||
@@ -220,7 +221,7 @@ pub async fn get_updates_for_view(
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
colored(&removing.join(", "), Color::Red)
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'users'\n{}: {removing}\n{}: {adding}",
|
||||
@@ -241,7 +242,7 @@ pub async fn get_updates_for_view(
|
||||
let adding = if adding.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&adding.join(", "), "green")
|
||||
colored(&adding.join(", "), Color::Green)
|
||||
};
|
||||
let removing = original_permissions
|
||||
.iter()
|
||||
@@ -253,7 +254,7 @@ pub async fn get_updates_for_view(
|
||||
let removing = if removing.is_empty() {
|
||||
String::from("None")
|
||||
} else {
|
||||
colored(&removing.join(", "), "red")
|
||||
colored(&removing.join(", "), Color::Red)
|
||||
};
|
||||
lines.push(format!(
|
||||
"{}: 'permissions'\n{}: {removing}\n{}: {adding}",
|
||||
@@ -270,7 +271,7 @@ pub async fn get_updates_for_view(
|
||||
for name in &to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: user group: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
colored("DELETE", Color::Red),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
@@ -495,7 +496,7 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group.name)
|
||||
));
|
||||
continue;
|
||||
@@ -503,7 +504,7 @@ pub async fn run_updates(
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
};
|
||||
@@ -562,14 +563,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete user group '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
bold(&user_group.name)
|
||||
))
|
||||
}
|
||||
@@ -602,14 +603,14 @@ async fn set_users(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set users in group {} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' users",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
@@ -636,14 +637,14 @@ async fn run_update_permissions(
|
||||
*has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to set permssion in group {} | target: {target:?} | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&user_group)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} user group '{}' permissions",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&user_group)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Context;
|
||||
use formatting::{bold, colored, muted, Color};
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
CreateVariable, DeleteVariable, UpdateVariableDescription,
|
||||
@@ -16,8 +17,6 @@ use resolver_api::Resolve;
|
||||
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
use super::{bold, colored, muted};
|
||||
|
||||
pub struct ToUpdateItem {
|
||||
pub variable: Variable,
|
||||
pub update_value: bool,
|
||||
@@ -66,7 +65,7 @@ pub async fn get_updates_for_view(
|
||||
update.to_update += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("UPDATE", "blue"),
|
||||
colored("UPDATE", Color::Blue),
|
||||
bold(&item.variable.name),
|
||||
));
|
||||
|
||||
@@ -77,9 +76,9 @@ pub async fn get_updates_for_view(
|
||||
"{}: 'value'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.value, "red"),
|
||||
colored(&original.value, Color::Red),
|
||||
muted("to"),
|
||||
colored(&item.variable.value, "green")
|
||||
colored(&item.variable.value, Color::Green)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -88,9 +87,9 @@ pub async fn get_updates_for_view(
|
||||
"{}: 'description'\n{}: {}\n{}: {}",
|
||||
muted("field"),
|
||||
muted("from"),
|
||||
colored(&original.description, "red"),
|
||||
colored(&original.description, Color::Red),
|
||||
muted("to"),
|
||||
colored(&item.variable.description, "green")
|
||||
colored(&item.variable.description, Color::Green)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -99,15 +98,25 @@ pub async fn get_updates_for_view(
|
||||
}
|
||||
None => {
|
||||
update.to_create += 1;
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
colored("CREATE", "green"),
|
||||
colored(&variable.name, "green"),
|
||||
muted("description"),
|
||||
variable.description,
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
if variable.description.is_empty() {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}",
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&variable.name, Color::Green),
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
} else {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
|
||||
colored("CREATE", Color::Green),
|
||||
colored(&variable.name, Color::Green),
|
||||
muted("description"),
|
||||
variable.description,
|
||||
muted("value"),
|
||||
variable.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +124,7 @@ pub async fn get_updates_for_view(
|
||||
for name in &to_delete {
|
||||
update.log.push_str(&format!(
|
||||
"\n\n{}: variable: '{}'\n-------------------",
|
||||
colored("DELETE", "red"),
|
||||
colored("DELETE", Color::Red),
|
||||
bold(name),
|
||||
));
|
||||
}
|
||||
@@ -202,14 +211,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to create variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable.name)
|
||||
));
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("created", "green"),
|
||||
colored("created", Color::Green),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
@@ -235,14 +244,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable value for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' value",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
@@ -261,14 +270,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to update variable description for '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable.name)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}' description",
|
||||
muted("INFO"),
|
||||
colored("updated", "blue"),
|
||||
colored("updated", Color::Blue),
|
||||
bold(&variable.name)
|
||||
))
|
||||
};
|
||||
@@ -288,14 +297,14 @@ pub async fn run_updates(
|
||||
has_error = true;
|
||||
log.push_str(&format!(
|
||||
"\n{}: failed to delete variable '{}' | {e:#}",
|
||||
colored("ERROR", "red"),
|
||||
colored("ERROR", Color::Red),
|
||||
bold(&variable)
|
||||
))
|
||||
} else {
|
||||
log.push_str(&format!(
|
||||
"\n{}: {} variable '{}'",
|
||||
muted("INFO"),
|
||||
colored("deleted", "red"),
|
||||
colored("deleted", Color::Red),
|
||||
bold(&variable)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::Router;
|
||||
use termination_signal::tokio::immediate_term_handle;
|
||||
use tower_http::{
|
||||
cors::{Any, CorsLayer},
|
||||
services::{ServeDir, ServeFile},
|
||||
@@ -77,13 +76,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let term_signal = immediate_term_handle()?;
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
_ = term_signal => {},
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -52,7 +52,6 @@ pub async fn insert_repos_status_unknown(repos: Vec<Repo>) {
|
||||
.insert(
|
||||
repo.id.clone(),
|
||||
CachedRepoStatus {
|
||||
id: repo.id,
|
||||
latest_hash: None,
|
||||
latest_message: None,
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ pub struct CachedDeploymentStatus {
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct CachedRepoStatus {
|
||||
pub id: String,
|
||||
pub latest_hash: Option<String>,
|
||||
pub latest_message: Option<String>,
|
||||
}
|
||||
@@ -229,9 +228,8 @@ pub async fn update_cache_for_server(server: &Server) {
|
||||
.unzip();
|
||||
status_cache
|
||||
.insert(
|
||||
repo.id.clone(),
|
||||
repo.id,
|
||||
CachedRepoStatus {
|
||||
id: repo.id,
|
||||
latest_hash,
|
||||
latest_message,
|
||||
}
|
||||
|
||||
@@ -410,7 +410,7 @@ pub async fn update<T: MonitorResource>(
|
||||
|
||||
for FieldDiff { field, from, to } in diff.iter_field_diffs() {
|
||||
diff_log.push_str(&format!(
|
||||
"\n\n<span class=\"text-muted-foreground\">field</span>: '{field}'\n<span class=\"text-muted-foreground\">from</span>: <span class=\"text-red-500\">{from}</span>\n<span class=\"text-muted-foreground\">to</span>: <span class=\"text-green-500\">{to}</span>",
|
||||
"\n\n<span class=\"text-muted-foreground\">field</span>: '{field}'\n<span class=\"text-muted-foreground\">from</span>: <span class=\"text-red-700 dark:text-red-400\">{from}</span>\n<span class=\"text-muted-foreground\">to</span>: <span class=\"text-green-700 dark:text-green-400\">{to}</span>",
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ repository.workspace = true
|
||||
monitor_client.workspace = true
|
||||
logger.workspace = true
|
||||
#
|
||||
termination_signal.workspace = true
|
||||
mungos.workspace = true
|
||||
#
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#[allow(unused)]
|
||||
pub mod v0;
|
||||
pub mod v1_6;
|
||||
|
||||
@@ -25,7 +25,6 @@ serror = { workspace = true, features = ["axum"] }
|
||||
async_timing_util.workspace = true
|
||||
merge_config_files.workspace = true
|
||||
parse_csl.workspace = true
|
||||
termination_signal.workspace = true
|
||||
run_command.workspace = true
|
||||
svi.workspace = true
|
||||
resolver_api.workspace = true
|
||||
|
||||
@@ -51,14 +51,17 @@ impl Resolve<GetContainerLogSearch> for State {
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
req: GetContainerLogSearch,
|
||||
GetContainerLogSearch {
|
||||
name,
|
||||
terms,
|
||||
combinator,
|
||||
invert,
|
||||
}: GetContainerLogSearch,
|
||||
_: (),
|
||||
) -> anyhow::Result<Log> {
|
||||
Ok(
|
||||
docker::container::container_log_search(
|
||||
&req.name,
|
||||
&req.terms,
|
||||
req.combinator,
|
||||
&name, &terms, combinator, invert,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
|
||||
@@ -27,13 +27,18 @@ pub async fn container_log_search(
|
||||
container_name: &str,
|
||||
terms: &[String],
|
||||
combinator: SearchCombinator,
|
||||
invert: bool,
|
||||
) -> Log {
|
||||
let maybe_invert = invert.then_some(" -v").unwrap_or_default();
|
||||
let grep = match combinator {
|
||||
SearchCombinator::Or => {
|
||||
format!("grep -E '{}'", terms.join("|"))
|
||||
format!("grep{maybe_invert} -E '{}'", terms.join("|"))
|
||||
}
|
||||
SearchCombinator::And => {
|
||||
format!("grep -P '^(?=.*{})'", terms.join(")(?=.*"))
|
||||
format!(
|
||||
"grep{maybe_invert} -P '^(?=.*{})'",
|
||||
terms.join(")(?=.*")
|
||||
)
|
||||
}
|
||||
};
|
||||
let command =
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{middleware, routing::post, Router};
|
||||
use termination_signal::tokio::immediate_term_handle;
|
||||
|
||||
mod api;
|
||||
mod config;
|
||||
@@ -52,13 +51,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let term_signal = immediate_term_handle()?;
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
_ = term_signal => {},
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -13,8 +13,6 @@ repository.workspace = true
|
||||
# local
|
||||
monitor_client.workspace = true
|
||||
logger.workspace = true
|
||||
# mogh
|
||||
termination_signal.workspace = true
|
||||
# external
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
extern crate tracing;
|
||||
|
||||
use monitor_client::MonitorClient;
|
||||
use termination_signal::tokio::immediate_term_handle;
|
||||
|
||||
async fn app() -> anyhow::Result<()> {
|
||||
logger::init(&Default::default())?;
|
||||
@@ -27,13 +26,15 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let term_signal = immediate_term_handle()?;
|
||||
let mut term_signal = tokio::signal::unix::signal(
|
||||
tokio::signal::unix::SignalKind::terminate(),
|
||||
)?;
|
||||
|
||||
let app = tokio::spawn(app());
|
||||
|
||||
tokio::select! {
|
||||
res = app => return res?,
|
||||
_ = term_signal => {},
|
||||
res = app => return res?,
|
||||
_ = term_signal.recv() => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Changelog
|
||||
|
||||
## <ins>v1 (Spring 2024)</ins>
|
||||
|
||||
- **New resource types**:
|
||||
- **Repo**: Clone / pull configured repositories on desired Server. Run shell commands in the repo on every clone / pull to acheive automation. Listen for pushes to a particular branch to automatically pull the repo and run the command.
|
||||
- **Procedure**: Combine multiple *executions* (Deploy, RunBuild) and run them in sequence or in parallel. *RunProcedure* is an execution type, meaning procedures can run *other procedures*.
|
||||
- **Builder**: Ephemeral builder configuration has moved to being an API / UI managed entity for greater observability and ease of management.
|
||||
- **Alerter**: Define multiple alerting endpoints and manage them via the API / UI.
|
||||
- Slack support continues with the *Slack* Alerter variant.
|
||||
- Send JSON serialized alert data to any HTTP endpoint with the *Custom* Alerter variant.
|
||||
- **Template**: Define a template for your cloud provider's VM configuration
|
||||
- Launch VMs based on the template and automatically add them as Monitor servers.
|
||||
- Supports AWS EC2 and Hetzner Cloud.
|
||||
- **Sync**: Sync resources declared in toml files in Github repos.
|
||||
- Manage resources declaratively, with git history for configuration rollbacks.
|
||||
- See the actions which will be performed in the UI, and execute them upon manual confirmation.
|
||||
- Use a Github webhook to automatically execute syncs on git push.
|
||||
|
||||
- **Resource Tagging**
|
||||
- Attach multiple *tags* to resources, which can be used to group related resources together. These can be used to filter resources in the UI.
|
||||
- For example, resources can be given tags for *environment*, like `Prod`, `Uat`, or `Dev`. This can be combined with tags for the larger system the resource is a part of, such as `Authentication`, `Logging`, or `Messaging`.
|
||||
- Proper tagging will make it easy to find resources and visualize system components, even as the number of resources grows large.
|
||||
|
||||
- **Variables**
|
||||
- Manage global, non-secret key-value pairs using the API / UI.
|
||||
- These values can be interpolated into deployment `environments` and build `build_args`
|
||||
|
||||
- **Core Accounts and Secrets**
|
||||
- Docker / Github accounts and Secrets can now be configured in the Core configuration file.
|
||||
- They can still be added to the Periphery configuration as before. Accounts / Secrets defined in the Core configuration will be preferentially used over like ones defined in Periphery configuration.
|
||||
|
||||
- **User Groups**
|
||||
- Admins can now create User Groups and assign permissions to them as if they were a user.
|
||||
- Multiple users can then be added to the group, and a user can be added to multiple groups.
|
||||
- Users in the group inherit the group's permissions.
|
||||
|
||||
- **Builds**
|
||||
- Build log displays the **latest commit hash and message**.
|
||||
- In-progress builds are able to be **cancelled before completion**.
|
||||
- Specify a specific commit hash to build from.
|
||||
|
||||
- **Deployments**
|
||||
- Filter log lines using multiple search terms.
|
||||
|
||||
- **Alerting**
|
||||
- The alerting system has been redesigned with a stateful model.
|
||||
- Alerts can be in an Open or a Resolved state, and alerts are only sent on state changes.
|
||||
- For example, say a server has just crossed 80% memory usage, the configured memory threshold. An alert will be created in the Open state and the alert data will be sent out. Later, it has dropped down to 70%. The alert will be changed to the Resolved state and the alert data will again be sent.
|
||||
- In addition to server usage alerts, Monitor now supports deployment state change alerts. These are sent when a deployment's state changes without being caused by a Monitor action. For example, if a deployment goes from the Running state to the Exited state unexpectedly, say from a crash, an alert will be sent.
|
||||
- Current and past alerts can be retrieved using the API and viewed on the UI.
|
||||
|
||||
- **New UI**:
|
||||
- The Monitor UI has been revamped to support the new features and improve the user experience.
|
||||
|
||||
## <ins>v0 (Winter 2022)</ins>
|
||||
|
||||
- Move Core and Periphery implementation to Rust.
|
||||
- Add AWS Ec2 ephemeral build instance support.
|
||||
- Configuration added to core configuration file.
|
||||
- Automatic build versioning system, supporting image rollback.
|
||||
- Realtime and historical system stats - CPU, memory, disk.
|
||||
- Simple stats alerting based on out-of-bounds values for system stats.
|
||||
- Support sending alerts to Slack.
|
||||
|
||||
## <ins>Pre-versioned releases</ins>
|
||||
|
||||
- Defined main resource types:
|
||||
- Server
|
||||
- Deployment
|
||||
- Build
|
||||
- Basics of Monitor:
|
||||
- Build docker images from Github repos.
|
||||
- Manage image deployment on connected servers, see container status, get container logs.
|
||||
- Add account credentials in Periphery configuration.
|
||||
- Core and Periphery implemented in Typescript.
|
||||
@@ -196,6 +196,8 @@ pub struct GetBuildVersions {
|
||||
pub minor: Option<i32>,
|
||||
/// Filter to only include versions matching this patch version.
|
||||
pub patch: Option<i32>,
|
||||
/// Limit the number of included results. Default is no limit.
|
||||
pub limit: Option<I64>,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
||||
@@ -153,6 +153,9 @@ pub struct SearchLog {
|
||||
/// - `OR`: Include lines that have one or more matches in the terms.
|
||||
#[serde(default)]
|
||||
pub combinator: SearchCombinator,
|
||||
/// Invert the results, ie return all lines that DON'T match the terms / combinator.
|
||||
#[serde(default)]
|
||||
pub invert: bool,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
|
||||
@@ -299,11 +299,15 @@ pub fn conversions_to_string(conversions: &[Conversion]) -> String {
|
||||
pub fn conversions_from_str(
|
||||
value: &str,
|
||||
) -> anyhow::Result<Vec<Conversion>> {
|
||||
let res = value
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let res = trimmed
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.starts_with('#'))
|
||||
.filter(|(_, line)| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let local = split
|
||||
@@ -604,11 +608,15 @@ pub fn term_signal_labels_to_string(
|
||||
pub fn term_signal_labels_from_str(
|
||||
value: &str,
|
||||
) -> anyhow::Result<Vec<TerminationSignalLabel>> {
|
||||
let res = value
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let res = trimmed
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.starts_with('#'))
|
||||
.filter(|(_, line)| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let signal = split
|
||||
|
||||
@@ -44,7 +44,7 @@ pub mod resource;
|
||||
pub mod server;
|
||||
/// Subtypes of [ServerTemplate][server_template::ServerTemplate].
|
||||
pub mod server_template;
|
||||
/// Subtypes of [mod@Sync][sync::Sync]
|
||||
/// Subtypes of [ResourceSync][sync::ResourceSync]
|
||||
pub mod sync;
|
||||
/// Subtypes of [Tag][tag::Tag].
|
||||
pub mod tag;
|
||||
@@ -347,11 +347,15 @@ pub fn environment_vars_to_string(vars: &[EnvironmentVar]) -> String {
|
||||
pub fn environment_vars_from_str(
|
||||
value: &str,
|
||||
) -> anyhow::Result<Vec<EnvironmentVar>> {
|
||||
let res = value
|
||||
let trimmed = value.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let res = trimmed
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.enumerate()
|
||||
.filter(|(_, line)| !line.starts_with('#'))
|
||||
.filter(|(_, line)| !line.is_empty() && !line.starts_with('#'))
|
||||
.map(|(i, line)| {
|
||||
let mut split = line.split('=');
|
||||
let variable = split
|
||||
@@ -496,6 +500,7 @@ pub struct CloneArgs {
|
||||
pub repo: Option<String>,
|
||||
pub branch: Option<String>,
|
||||
pub commit: Option<String>,
|
||||
pub destination: Option<String>,
|
||||
pub on_clone: Option<SystemCommand>,
|
||||
pub on_pull: Option<SystemCommand>,
|
||||
pub github_account: Option<String>,
|
||||
@@ -508,6 +513,7 @@ impl From<&self::build::Build> for CloneArgs {
|
||||
repo: optional_string(&build.config.repo),
|
||||
branch: optional_string(&build.config.branch),
|
||||
commit: optional_string(&build.config.commit),
|
||||
destination: None,
|
||||
on_clone: build.config.pre_build.clone().into_option(),
|
||||
on_pull: None,
|
||||
github_account: optional_string(&build.config.github_account),
|
||||
@@ -522,6 +528,7 @@ impl From<&self::repo::Repo> for CloneArgs {
|
||||
repo: optional_string(&repo.config.repo),
|
||||
branch: optional_string(&repo.config.branch),
|
||||
commit: optional_string(&repo.config.commit),
|
||||
destination: optional_string(&repo.config.path),
|
||||
on_clone: repo.config.on_clone.clone().into_option(),
|
||||
on_pull: repo.config.on_pull.clone().into_option(),
|
||||
github_account: optional_string(&repo.config.github_account),
|
||||
@@ -537,6 +544,7 @@ impl From<&self::sync::ResourceSync> for CloneArgs {
|
||||
branch: optional_string(&sync.config.branch),
|
||||
commit: optional_string(&sync.config.commit),
|
||||
github_account: optional_string(&sync.config.github_account),
|
||||
destination: None,
|
||||
on_clone: None,
|
||||
on_pull: None,
|
||||
}
|
||||
|
||||
@@ -99,6 +99,11 @@ pub struct RepoConfig {
|
||||
#[builder(default)]
|
||||
pub github_account: String,
|
||||
|
||||
/// Explicitly specificy the folder to clone the repo in.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub path: String,
|
||||
|
||||
/// Command to be run after the repo is cloned.
|
||||
/// The path is relative to the root of the repo.
|
||||
#[serde(default)]
|
||||
@@ -140,6 +145,7 @@ impl Default for RepoConfig {
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
github_account: Default::default(),
|
||||
path: Default::default(),
|
||||
on_clone: Default::default(),
|
||||
on_pull: Default::default(),
|
||||
webhook_enabled: default_webhook_enabled(),
|
||||
|
||||
@@ -97,19 +97,37 @@ pub struct ResourceToml<PartialConfig: Default> {
|
||||
/// The resource name. Required
|
||||
pub name: String,
|
||||
|
||||
/// The resource description.
|
||||
#[serde(default)]
|
||||
/// The resource description. Optional.
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub description: String,
|
||||
|
||||
/// Tag ids or names
|
||||
#[serde(default)]
|
||||
/// Tag ids or names. Optional
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// Resource specific configuration
|
||||
/// Optional. Only relevant for deployments.
|
||||
///
|
||||
/// Will ensure deployment is running with the latest configuration.
|
||||
/// Deploy actions to achieve this will be included in the sync.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub deploy: bool,
|
||||
|
||||
/// Optional. Only relevant for deployments using the 'deploy' sync feature.
|
||||
///
|
||||
/// Specify other deployments as dependencies.
|
||||
/// The sync will ensure the deployment will only be deployed 'after' its dependencies.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub after: Vec<String>,
|
||||
|
||||
/// Resource specific configuration.
|
||||
#[serde(default)]
|
||||
pub config: PartialConfig,
|
||||
}
|
||||
|
||||
fn is_false(b: &bool) -> bool {
|
||||
!b
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserGroupToml {
|
||||
/// User group name
|
||||
|
||||
@@ -25,9 +25,12 @@ function fix_types() {
|
||||
const contents = readFileSync(types_path);
|
||||
const fixed = contents
|
||||
.toString()
|
||||
// Replace Variants
|
||||
.replaceAll("ResourceTargetVariant", 'ResourceTarget["type"]')
|
||||
.replaceAll("AlerterEndpointVariant", 'AlerterEndpoint["type"]')
|
||||
.replaceAll("AlertDataVariant", 'AlertData["type"]')
|
||||
.replaceAll("ServerTemplateConfigVariant", 'ServerTemplateConfig["type"]');
|
||||
.replaceAll("ServerTemplateConfigVariant", 'ServerTemplateConfig["type"]')
|
||||
// Add '| string' to env vars
|
||||
.replaceAll("EnvironmentVar[]", "EnvironmentVar[] | string");
|
||||
writeFileSync(types_path, fixed);
|
||||
}
|
||||
|
||||
+69
-62
@@ -236,7 +236,7 @@ export interface Resource<Config, Info> {
|
||||
/** Resource-specific information (not user configurable). */
|
||||
info?: Info;
|
||||
/** Resource-specific configuration. */
|
||||
config: Config;
|
||||
config?: Config;
|
||||
}
|
||||
|
||||
export type AlerterEndpoint =
|
||||
@@ -248,6 +248,12 @@ export type AlerterEndpoint =
|
||||
export interface AlerterConfig {
|
||||
/** Whether the alerter is enabled */
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Where to route the alert messages.
|
||||
*
|
||||
* Default: Custom endpoint `http://localhost:7000`
|
||||
*/
|
||||
endpoint?: AlerterEndpoint;
|
||||
/**
|
||||
* Only send specific alert types.
|
||||
* If empty, will send all alert types.
|
||||
@@ -258,12 +264,6 @@ export interface AlerterConfig {
|
||||
* If empty, will send alerts for all resources.
|
||||
*/
|
||||
resources?: ResourceTarget[];
|
||||
/**
|
||||
* Where to route the alert messages.
|
||||
*
|
||||
* Default: Custom endpoint `http://localhost:7000`
|
||||
*/
|
||||
endpoint?: AlerterEndpoint;
|
||||
}
|
||||
|
||||
export type Alerter = Resource<AlerterConfig, undefined>;
|
||||
@@ -307,11 +307,6 @@ export interface SystemCommand {
|
||||
command?: string;
|
||||
}
|
||||
|
||||
export interface EnvironmentVar {
|
||||
variable: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Configuration for the registry to push the built image to. */
|
||||
export type ImageRegistry =
|
||||
/** Don't push the image to any registry */
|
||||
@@ -328,12 +323,15 @@ export type ImageRegistry =
|
||||
/** Todo. Will point to a custom "Registry" resource by id */
|
||||
| { type: "Custom", params: string };
|
||||
|
||||
export interface EnvironmentVar {
|
||||
variable: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** The build configuration. */
|
||||
export interface BuildConfig {
|
||||
/** Which builder is used to build the image. */
|
||||
builder_id?: string;
|
||||
/** Whether to skip secret interpolation in the build_args. */
|
||||
skip_secret_interp?: boolean;
|
||||
/** The current version of the build. */
|
||||
version?: Version;
|
||||
/** The Github repo used as the source of the build. */
|
||||
@@ -349,6 +347,8 @@ export interface BuildConfig {
|
||||
github_account?: string;
|
||||
/** The optional command run after repo clone and before docker build. */
|
||||
pre_build?: SystemCommand;
|
||||
/** Configuration for the registry to push the built image to. */
|
||||
image_registry?: ImageRegistry;
|
||||
/**
|
||||
* The path of the docker build context relative to the root of the repo.
|
||||
* Default: "." (the root of the repo).
|
||||
@@ -356,18 +356,18 @@ export interface BuildConfig {
|
||||
build_path: string;
|
||||
/** The path of the dockerfile relative to the build path. */
|
||||
dockerfile_path: string;
|
||||
/** Docker build arguments */
|
||||
build_args?: EnvironmentVar[];
|
||||
/** Docker labels */
|
||||
labels?: EnvironmentVar[];
|
||||
/** Any extra docker cli arguments to be included in the build command */
|
||||
extra_args?: string[];
|
||||
/** Whether to skip secret interpolation in the build_args. */
|
||||
skip_secret_interp?: boolean;
|
||||
/** Whether to use buildx to build (eg `docker buildx build ...`) */
|
||||
use_buildx?: boolean;
|
||||
/** Configuration for the registry to push the built image to. */
|
||||
image_registry?: ImageRegistry;
|
||||
/** Whether incoming webhooks actually trigger action. */
|
||||
webhook_enabled: boolean;
|
||||
/** Any extra docker cli arguments to be included in the build command */
|
||||
extra_args?: string[];
|
||||
/** Docker build arguments */
|
||||
build_args?: EnvironmentVar[] | string;
|
||||
/** Docker labels */
|
||||
labels?: EnvironmentVar[] | string;
|
||||
}
|
||||
|
||||
export interface BuildInfo {
|
||||
@@ -465,6 +465,13 @@ export type DeploymentImage =
|
||||
version?: Version;
|
||||
}};
|
||||
|
||||
export enum RestartMode {
|
||||
NoRestart = "no",
|
||||
OnFailure = "on-failure",
|
||||
Always = "always",
|
||||
UnlessStopped = "unless-stopped",
|
||||
}
|
||||
|
||||
export enum TerminationSignal {
|
||||
SigHup = "SIGHUP",
|
||||
SigInt = "SIGINT",
|
||||
@@ -484,18 +491,9 @@ export interface Conversion {
|
||||
container: string;
|
||||
}
|
||||
|
||||
export enum RestartMode {
|
||||
NoRestart = "no",
|
||||
OnFailure = "on-failure",
|
||||
Always = "always",
|
||||
UnlessStopped = "unless-stopped",
|
||||
}
|
||||
|
||||
export interface DeploymentConfig {
|
||||
/** The id of server the deployment is deployed on. */
|
||||
server_id?: string;
|
||||
/** Whether to send ContainerStateChange alerts for this deployment. */
|
||||
send_alerts: boolean;
|
||||
/**
|
||||
* The image which the deployment deploys.
|
||||
* Can either be a user inputted image, or a Monitor build.
|
||||
@@ -515,30 +513,8 @@ export interface DeploymentConfig {
|
||||
skip_secret_interp?: boolean;
|
||||
/** Whether to redeploy the deployment whenever the attached build finishes. */
|
||||
redeploy_on_build?: boolean;
|
||||
/**
|
||||
* Labels attached to various termination signal options.
|
||||
* Used to specify different shutdown functionality depending on the termination signal.
|
||||
*/
|
||||
term_signal_labels: TerminationSignalLabel[];
|
||||
/** The default termination signal to use to stop the deployment. Defaults to SigTerm (default docker signal). */
|
||||
termination_signal?: TerminationSignal;
|
||||
/** The termination timeout. */
|
||||
termination_timeout: number;
|
||||
/**
|
||||
* The container port mapping.
|
||||
* Irrelevant if container network is `host`.
|
||||
* Maps ports on host to ports on container.
|
||||
*/
|
||||
ports?: Conversion[];
|
||||
/**
|
||||
* The container volume mapping.
|
||||
* Maps files / folders on host to files / folders in container.
|
||||
*/
|
||||
volumes?: Conversion[];
|
||||
/** The environment variables passed to the container. */
|
||||
environment?: EnvironmentVar[];
|
||||
/** The docker labels given to the container. */
|
||||
labels?: EnvironmentVar[];
|
||||
/** Whether to send ContainerStateChange alerts for this deployment. */
|
||||
send_alerts: boolean;
|
||||
/**
|
||||
* The network attached to the container.
|
||||
* Default is `host`.
|
||||
@@ -553,11 +529,35 @@ export interface DeploymentConfig {
|
||||
* Empty is no command.
|
||||
*/
|
||||
command?: string;
|
||||
/** The default termination signal to use to stop the deployment. Defaults to SigTerm (default docker signal). */
|
||||
termination_signal?: TerminationSignal;
|
||||
/** The termination timeout. */
|
||||
termination_timeout: number;
|
||||
/**
|
||||
* Extra args which are interpolated into the `docker run` command,
|
||||
* and affect the container configuration.
|
||||
*/
|
||||
extra_args?: string[];
|
||||
/**
|
||||
* Labels attached to various termination signal options.
|
||||
* Used to specify different shutdown functionality depending on the termination signal.
|
||||
*/
|
||||
term_signal_labels: TerminationSignalLabel[];
|
||||
/**
|
||||
* The container port mapping.
|
||||
* Irrelevant if container network is `host`.
|
||||
* Maps ports on host to ports on container.
|
||||
*/
|
||||
ports?: Conversion[];
|
||||
/**
|
||||
* The container volume mapping.
|
||||
* Maps files / folders on host to files / folders in container.
|
||||
*/
|
||||
volumes?: Conversion[];
|
||||
/** The environment variables passed to the container. */
|
||||
environment?: EnvironmentVar[] | string;
|
||||
/** The docker labels given to the container. */
|
||||
labels?: EnvironmentVar[] | string;
|
||||
}
|
||||
|
||||
export type Deployment = Resource<DeploymentConfig, undefined>;
|
||||
@@ -782,6 +782,8 @@ export interface RepoConfig {
|
||||
* It must be available in the server's periphery config.
|
||||
*/
|
||||
github_account?: string;
|
||||
/** Explicitly specificy the folder to clone the repo in. */
|
||||
path?: string;
|
||||
/**
|
||||
* Command to be run after the repo is cloned.
|
||||
* The path is relative to the root of the repo.
|
||||
@@ -857,6 +859,8 @@ export interface ServerConfig {
|
||||
* Example: http://localhost:8120
|
||||
*/
|
||||
address: string;
|
||||
/** An optional region label */
|
||||
region?: string;
|
||||
/**
|
||||
* Whether a server is enabled.
|
||||
* If a server is disabled,
|
||||
@@ -882,8 +886,6 @@ export interface ServerConfig {
|
||||
send_mem_alerts: boolean;
|
||||
/** Whether to send alerts about the servers DISK status */
|
||||
send_disk_alerts: boolean;
|
||||
/** An optional region label */
|
||||
region?: string;
|
||||
/** The percentage threshhold which triggers WARNING state for CPU. */
|
||||
cpu_warning: number;
|
||||
/** The percentage threshhold which triggers CRITICAL state for CPU. */
|
||||
@@ -2039,6 +2041,8 @@ export interface GetBuildVersions {
|
||||
minor?: number;
|
||||
/** Filter to only include versions matching this patch version. */
|
||||
patch?: number;
|
||||
/** Limit the number of included results. Default is no limit. */
|
||||
limit?: I64;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2193,6 +2197,8 @@ export interface SearchLog {
|
||||
* - `OR`: Include lines that have one or more matches in the terms.
|
||||
*/
|
||||
combinator?: SearchCombinator;
|
||||
/** Invert the results, ie return all lines that DON'T match the terms / combinator. */
|
||||
invert?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3630,11 +3636,6 @@ export interface AwsBuilderConfig {
|
||||
ami_id: string;
|
||||
/** The subnet id to create the instance in. */
|
||||
subnet_id: string;
|
||||
/**
|
||||
* The security group ids to attach to the instance.
|
||||
* This should include a security group to allow core inbound access to the periphery port.
|
||||
*/
|
||||
security_group_ids: string[];
|
||||
/** The key pair name to attach to the instance */
|
||||
key_pair_name: string;
|
||||
/**
|
||||
@@ -3647,6 +3648,11 @@ export interface AwsBuilderConfig {
|
||||
* If false, core will communicate with the instance using the private IP.
|
||||
*/
|
||||
use_public_ip: boolean;
|
||||
/**
|
||||
* The security group ids to attach to the instance.
|
||||
* This should include a security group to allow core inbound access to the periphery port.
|
||||
*/
|
||||
security_group_ids: string[];
|
||||
/** Which github accounts (usernames) are available on the AMI */
|
||||
github_accounts?: string[];
|
||||
/** Which dockerhub accounts (usernames) are available on the AMI */
|
||||
@@ -3658,6 +3664,7 @@ export interface CloneArgs {
|
||||
repo?: string;
|
||||
branch?: string;
|
||||
commit?: string;
|
||||
destination?: string;
|
||||
on_clone?: SystemCommand;
|
||||
on_pull?: SystemCommand;
|
||||
github_account?: string;
|
||||
|
||||
@@ -36,6 +36,8 @@ pub struct GetContainerLogSearch {
|
||||
pub terms: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub combinator: SearchCombinator,
|
||||
#[serde(default)]
|
||||
pub invert: bool,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -4,7 +4,31 @@ To run Monitor Core, you will need:
|
||||
|
||||
- A valid configuration file.
|
||||
- An instance of MongoDB to which Core can connect.
|
||||
- Docker must be installed on the host.
|
||||
- Docker must be installed on the host. See [the install docs](https://docs.docker.com/engine/install/).
|
||||
|
||||
## Mongo
|
||||
|
||||
Mongo can be run locally using the docker cli:
|
||||
|
||||
```sh
|
||||
docker run --name monitor-mongo \
|
||||
--network host \
|
||||
-v /local/storage/path:/data/db \
|
||||
-e MONGO_INITDB_ROOT_USERNAME="admin" \
|
||||
-e MONGO_INITDB_ROOT_PASSWORD="admin" \
|
||||
mongo:latest
|
||||
```
|
||||
|
||||
You should replace the username and password with your own.
|
||||
See [the image docs](https://hub.docker.com/_/mongo) for more details.
|
||||
|
||||
:::note
|
||||
The disk space requirements of Monitor are dominated by the storage of system stats.
|
||||
This depends on the number of connected servers (more system stats being produces / stored), stats collection frequency, and your stats pruning configuration.
|
||||
If you need to save on space, you can configure these fields in your core config:
|
||||
- Stats poll frequency can be reduced using, for example, `monitoring_interval = "15-sec"`
|
||||
- Pruning can be tuned more aggresively using, for example, `keep_stats_for_days = 7`.
|
||||
:::
|
||||
|
||||
## 1. Create the configuration file
|
||||
|
||||
@@ -29,8 +53,8 @@ Monitor core is distributed via Github Container Registry under the package [mbe
|
||||
|
||||
```sh
|
||||
docker run -d --name monitor-core \
|
||||
--network host \
|
||||
-v $HOME/.monitor/core.config.toml:/config/config.toml \
|
||||
-p 9000:9000 \
|
||||
ghcr.io/mbecker20/monitor_core
|
||||
```
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ Monitor is a web app to provide structure for managing your servers, builds, dep
|
||||
|
||||
With Monitor you can:
|
||||
|
||||
1. Build application source into auto-versioned images.
|
||||
2. Create, start, stop, and restart Docker containers, and view their status and logs.
|
||||
3. Manage repositories on remote servers.
|
||||
4. Keep a record of all the actions that are performed and by whom.
|
||||
5. View realtime and historical system resource usage, and alert for out of bounds values.
|
||||
1. Connect all of your remote servers (across clouds + on premise) to be viewable from a unified 'monitor' (hence the name).
|
||||
2. Build application source into auto-versioned Docker images.
|
||||
3. Create, start, stop, and restart Docker containers on the connected servers, and view their status and logs.
|
||||
4. Manage repositories on connected servers.
|
||||
5. Keep a record of all the actions that are performed and by whom.
|
||||
|
||||
## Docker
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Resources
|
||||
|
||||
Entities like `Server`, `Deployment`, and `Build` are abstracted under a `Resource`. A server is a type of resource, a build is a type of resource, and so on.
|
||||
All resources have common traits, such as a globally unique `name` and `id` amongst all other resources of the same resource type.
|
||||
All resources can also be assigned `tags`, which can be used to group related resources.
|
||||
For example, they can be grouped by environment (using `dev` or `prod` tags), or by function (`auth` or `ingress`),
|
||||
or really by any way that fits best for your infra (they are all user defined).
|
||||
|
||||
Here is a list of the resources and their description:
|
||||
- `Server`: Represents a connected server.
|
||||
- Holds server config, like the address.
|
||||
- `Deployment`: Represents a docker container on a server, whether it is actually deployed or not.
|
||||
- Holds deployment config, like the server it should deploy on, and the image / build to deploy.
|
||||
- `Build`: Represents a docker image.
|
||||
- Holds build config, like the source repo, Dockerfile location, and version
|
||||
- `Repo`: Represents a repo on a server, whether it is cloned or not.
|
||||
- Holds repo config, like the source repo, and the `on_clone` and `on_pull` commands, which run after the repo is cloned / pulled
|
||||
- `Procedure`: Configure higher level actions by composing lower level actions.
|
||||
- Holds the actions to execute, like `RunBuild build_1` and `Deploy deployment_1`, and the order to execute them
|
||||
- `Alerter`: Route the various alerts produced by monitor to alerting endpoints
|
||||
- Holds the alerting endpoint (Slack channel or Custom http POST), the alerting types to forward (eg. `ServerUnreachable` or `ContainerStateChange`).
|
||||
- `Builder`: Represents a server used as the "builder" for builds. Can be connected server or ephemeral AWS server.
|
||||
- Holds builder config, like the AWS ami-id and security groups to allow for builder reachability.
|
||||
- `ServerTemplate`: Configure cloud server templates (currently AWS and Hetzner) to easily launch more instances and auto connect them to Monitor
|
||||
- Holds the cloud server config
|
||||
- `ResourceSync`: Declare Monitor resources in TOML files, push them to a Github Repo, and sync Monitor config from them.
|
||||
- Holds config for the source repo containing the files. Will display the computed diff and wait for user to execute.
|
||||
@@ -1,4 +1,158 @@
|
||||
# Sync Resources
|
||||
|
||||
The [Montior CLI](https://crates.io/crates/monitor_cli) is able to sync Monitor resources declared in TOML files by diffing them against the resources
|
||||
returned by the API.
|
||||
Monitor is able to sync resources declared in TOML files by diffing them against the existing resources,
|
||||
and apply updates based on the diffs. Simply use a Github repo to manage the files and create a `ResourceSync` pointing to the repo,
|
||||
and the core backend will poll for any updates (you can also manually trigger an update poll / execution in the UI).
|
||||
|
||||
File detection is additive and recursive, so you can spread out your resource declarations across any number of files
|
||||
and use any nesting of folders to organize resources inside a root folder. Additionally, you can create multiple `ResourceSyncs`
|
||||
and each sync will be handled independently. This allows different syncs to manage resources on a "per-project" basis.
|
||||
|
||||
The UI will display the computed sync actions and only execute them upon manual confirmation.
|
||||
Or the sync execution Github webhook may be configured on the Github repo to
|
||||
automatically execute syncs upon pushes to the configured branch.
|
||||
|
||||
## Example file:
|
||||
|
||||
```toml
|
||||
[[server]] # Declare a new server
|
||||
name = "server-01"
|
||||
description = "the main mogh server"
|
||||
tags = ["monitor"]
|
||||
config.address = "http://localhost:8120"
|
||||
config.region = "AshburnDc1"
|
||||
config.enabled = true
|
||||
|
||||
##
|
||||
|
||||
[[build]]
|
||||
name = "test_logger"
|
||||
description = "Logs randomly at INFO, WARN, ERROR levels to test logging setups"
|
||||
tags = ["test"]
|
||||
config.builder_id = "builder-01"
|
||||
config.repo = "mbecker20/test_logger"
|
||||
config.branch = "master"
|
||||
config.github_account = "mbecker20"
|
||||
config.image_registry.type = "Ghcr"
|
||||
config.image_registry.params.account = "your_github_account"
|
||||
# Set docker labels
|
||||
config.labels = """
|
||||
org.opencontainers.image.source = https://github.com/mbecker20/test_logger
|
||||
org.opencontainers.image.description = Logs randomly at INFO, WARN, ERROR levels to test logging setups
|
||||
org.opencontainers.image.licenses = GPL-3.0"""
|
||||
|
||||
##
|
||||
|
||||
[[variable]] # Declare variables
|
||||
name = "OTLP_ENDPOINT"
|
||||
value = "http://localhost:4317"
|
||||
|
||||
##
|
||||
|
||||
[[deployment]] # Declare a deployment
|
||||
name = "test-logger-01"
|
||||
description = "test logger deployment 1"
|
||||
tags = ["test"]
|
||||
# sync will deploy the container:
|
||||
# - if it is not running.
|
||||
# - has relevant config updates.
|
||||
# - the attached build has new version.
|
||||
deploy = true
|
||||
config.server_id = "server-01"
|
||||
config.image.type = "Build"
|
||||
config.image.params.build = "test_logger"
|
||||
# set the volumes / bind mounts
|
||||
config.volumes = """
|
||||
/data/logs = /etc/logs
|
||||
/data/config = /etc/config"""
|
||||
# Set the environment variables
|
||||
config.environment = """
|
||||
OTLP_ENDPOINT = [[OTLP_ENDPOINT]] # interpolate variables into the envs. (they also support comments using '#')
|
||||
VARIABLE_1 = value_1
|
||||
VARIABLE_2 = value_2"""
|
||||
# Set Docker labels
|
||||
config.labels = "deployment.type = logger"
|
||||
|
||||
##
|
||||
|
||||
[[deployment]]
|
||||
name = "test-logger-02"
|
||||
description = "test logger deployment 2"
|
||||
tags = ["test"]
|
||||
deploy = true
|
||||
# Create a dependency on test-logger-01. This deployment will only be deployed after test-logger-01 is deployed.
|
||||
# Additionally, any sync deploy of test-logger-01 will also trigger sync deploy of this deployment.
|
||||
after = ["test-logger-01"]
|
||||
config.server_id = "server-01"
|
||||
config.image.type = "Build"
|
||||
config.image.params.build = "test_logger"
|
||||
config.volumes = """
|
||||
/data/logs = /etc/logs
|
||||
/data/config = /etc/config"""
|
||||
config.environment = """
|
||||
VARIABLE_1 = value_1
|
||||
VARIABLE_2 = value_2"""
|
||||
# Set Docker labels
|
||||
config.labels = "deployment.type = logger"
|
||||
|
||||
##
|
||||
|
||||
[[repo]]
|
||||
name = "monitor-periphery"
|
||||
description = "Builds new versions of the periphery binary. Requires Rust installed on the host."
|
||||
tags = ["monitor"]
|
||||
config.server_id = "server-01"
|
||||
config.repo = "mbecker20/monitor"
|
||||
# Run an action after the repo is pulled
|
||||
config.on_pull.path = "."
|
||||
config.on_pull.command = "/root/.cargo/bin/cargo build -p monitor_periphery --release && cp ./target/release/periphery /root/periphery"
|
||||
|
||||
##
|
||||
|
||||
[[procedure]]
|
||||
name = "test-procedure"
|
||||
description = "Do some things in a specific order"
|
||||
tags = ["test"]
|
||||
|
||||
# Each stage will be executed one after the other (in sequence)
|
||||
[[procedure.config.stage]]
|
||||
name = "Build stuff"
|
||||
enabled = true
|
||||
# The executions within a stage will be run in parallel. The stage completes when all executions finish.
|
||||
executions = [
|
||||
{ execution.type = "RunBuild", execution.params.build = "test_logger", enabled = true },
|
||||
{ execution.type = "PullRepo", execution.params.repo = "monitor-periphery", enabled = true },
|
||||
]
|
||||
|
||||
[[procedure.config.stage]]
|
||||
name = "Deploy test logger 1"
|
||||
enabled = true
|
||||
executions = [
|
||||
{ execution.type = "Deploy", execution.params.deployment = "test-logger-01", enabled = true }
|
||||
]
|
||||
|
||||
[[procedure.config.stage]]
|
||||
name = "Deploy test logger 2"
|
||||
enabled = true
|
||||
executions = [
|
||||
{ execution.type = "Deploy", execution.params.deployment = "test-logger-02", enabled = true }
|
||||
]
|
||||
|
||||
##
|
||||
|
||||
[[builder]] # Declare a builder
|
||||
name = "builder-01"
|
||||
tags = []
|
||||
config.type = "Aws"
|
||||
config.params.region = "us-east-2"
|
||||
config.params.ami_id = "ami-0e9bd154667944680"
|
||||
# These things come from your specific setup
|
||||
config.params.subnet_id = "subnet-xxxxxxxxxxxxxxxxxx"
|
||||
config.params.key_pair_name = "xxxxxxxx"
|
||||
config.params.assign_public_ip = true
|
||||
config.params.use_public_ip = true
|
||||
config.params.security_group_ids = [
|
||||
"sg-xxxxxxxxxxxxxxxxxx",
|
||||
"sg-xxxxxxxxxxxxxxxxxx"
|
||||
]
|
||||
```
|
||||
|
||||
Generated
+213
-210
@@ -8,20 +8,20 @@
|
||||
"name": "docsite",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.3.2",
|
||||
"@docusaurus/preset-classic": "^3.3.2",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"@docusaurus/core": "^3.4.0",
|
||||
"@docusaurus/preset-classic": "^3.4.0",
|
||||
"@mdx-js/react": "^3.0.1",
|
||||
"clsx": "^2.1.1",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.3.2",
|
||||
"@docusaurus/tsconfig": "^3.3.2",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@docusaurus/module-type-aliases": "^3.4.0",
|
||||
"@docusaurus/tsconfig": "^3.4.0",
|
||||
"@docusaurus/types": "^3.4.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"typescript": "~5.2.2"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
@@ -486,9 +486,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz",
|
||||
"integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz",
|
||||
"integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -1578,11 +1578,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-constant-elements": {
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz",
|
||||
"integrity": "sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz",
|
||||
"integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.0"
|
||||
"@babel/helper-plugin-utils": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2135,9 +2135,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/core": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.3.2.tgz",
|
||||
"integrity": "sha512-PzKMydKI3IU1LmeZQDi+ut5RSuilbXnA8QdowGeJEgU8EJjmx3rBHNT1LxQxOVqNEwpWi/csLwd9bn7rUjggPA==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/generator": "^7.23.3",
|
||||
@@ -2149,12 +2149,12 @@
|
||||
"@babel/runtime": "^7.22.6",
|
||||
"@babel/runtime-corejs3": "^7.22.6",
|
||||
"@babel/traverse": "^7.22.8",
|
||||
"@docusaurus/cssnano-preset": "3.3.2",
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/mdx-loader": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/cssnano-preset": "3.4.0",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/mdx-loader": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
@@ -2220,9 +2220,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/cssnano-preset": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.3.2.tgz",
|
||||
"integrity": "sha512-+5+epLk/Rp4vFML4zmyTATNc3Is+buMAL6dNjrMWahdJCJlMWMPd/8YfU+2PA57t8mlSbhLJ7vAZVy54cd1vRQ==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz",
|
||||
"integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==",
|
||||
"dependencies": {
|
||||
"cssnano-preset-advanced": "^6.1.2",
|
||||
"postcss": "^8.4.38",
|
||||
@@ -2234,9 +2234,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/logger": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.3.2.tgz",
|
||||
"integrity": "sha512-Ldu38GJ4P8g4guN7d7pyCOJ7qQugG7RVyaxrK8OnxuTlaImvQw33aDRwaX2eNmX8YK6v+//Z502F4sOZbHHCHQ==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz",
|
||||
"integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"tslib": "^2.6.0"
|
||||
@@ -2246,13 +2246,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/mdx-loader": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.3.2.tgz",
|
||||
"integrity": "sha512-AFRxj/aOk3/mfYDPxE3wTbrjeayVRvNSZP7mgMuUlrb2UlPRbSVAFX1k2RbgAJrnTSwMgb92m2BhJgYRfptN3g==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz",
|
||||
"integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"@mdx-js/mdx": "^3.0.0",
|
||||
"@slorber/remark-comment": "^1.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
@@ -2284,11 +2284,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/module-type-aliases": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.3.2.tgz",
|
||||
"integrity": "sha512-b/XB0TBJah5yKb4LYuJT4buFvL0MGAb0+vJDrJtlYMguRtsEBkf2nWl5xP7h4Dlw6ol0hsHrCYzJ50kNIOEclw==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz",
|
||||
"integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==",
|
||||
"dependencies": {
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
@@ -2302,17 +2302,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-blog": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.3.2.tgz",
|
||||
"integrity": "sha512-fJU+dmqp231LnwDJv+BHVWft8pcUS2xVPZdeYH6/ibH1s2wQ/sLcmUrGWyIv/Gq9Ptj8XWjRPMghlxghuPPoxg==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz",
|
||||
"integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/mdx-loader": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/mdx-loader": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"feed": "^4.2.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
@@ -2333,18 +2333,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-docs": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.3.2.tgz",
|
||||
"integrity": "sha512-Dm1ri2VlGATTN3VGk1ZRqdRXWa1UlFubjaEL6JaxaK7IIFqN/Esjpl+Xw10R33loHcRww/H76VdEeYayaL76eg==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz",
|
||||
"integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/mdx-loader": "3.3.2",
|
||||
"@docusaurus/module-type-aliases": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/mdx-loader": "3.4.0",
|
||||
"@docusaurus/module-type-aliases": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"@types/react-router-config": "^5.0.7",
|
||||
"combine-promises": "^1.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
@@ -2363,15 +2363,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-content-pages": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.3.2.tgz",
|
||||
"integrity": "sha512-EKc9fQn5H2+OcGER8x1aR+7URtAGWySUgULfqE/M14+rIisdrBstuEZ4lUPDRrSIexOVClML82h2fDS+GSb8Ew==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz",
|
||||
"integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/mdx-loader": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/mdx-loader": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"tslib": "^2.6.0",
|
||||
"webpack": "^5.88.1"
|
||||
@@ -2385,13 +2385,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-debug": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.3.2.tgz",
|
||||
"integrity": "sha512-oBIBmwtaB+YS0XlmZ3gCO+cMbsGvIYuAKkAopoCh0arVjtlyPbejzPrHuCoRHB9G7abjNZw7zoONOR8+8LM5+Q==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz",
|
||||
"integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"react-json-view-lite": "^1.2.0",
|
||||
"tslib": "^2.6.0"
|
||||
@@ -2405,13 +2405,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-google-analytics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.3.2.tgz",
|
||||
"integrity": "sha512-jXhrEIhYPSClMBK6/IA8qf1/FBoxqGXZvg7EuBax9HaK9+kL3L0TJIlatd8jQJOMtds8mKw806TOCc3rtEad1A==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz",
|
||||
"integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2423,13 +2423,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-google-gtag": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.3.2.tgz",
|
||||
"integrity": "sha512-vcrKOHGbIDjVnNMrfbNpRQR1x6Jvcrb48kVzpBAOsKbj9rXZm/idjVAXRaewwobHdOrJkfWS/UJoxzK8wyLRBQ==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz",
|
||||
"integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"@types/gtag.js": "^0.0.12",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
@@ -2442,13 +2442,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-google-tag-manager": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.3.2.tgz",
|
||||
"integrity": "sha512-ldkR58Fdeks0vC+HQ+L+bGFSJsotQsipXD+iKXQFvkOfmPIV6QbHRd7IIcm5b6UtwOiK33PylNS++gjyLUmaGw==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz",
|
||||
"integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2460,16 +2460,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/plugin-sitemap": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.3.2.tgz",
|
||||
"integrity": "sha512-/ZI1+bwZBhAgC30inBsHe3qY9LOZS+79fRGkNdTcGHRMcdAp6Vw2pCd1gzlxd/xU+HXsNP6cLmTOrggmRp3Ujg==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz",
|
||||
"integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"sitemap": "^7.1.1",
|
||||
"tslib": "^2.6.0"
|
||||
@@ -2483,23 +2483,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/preset-classic": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.3.2.tgz",
|
||||
"integrity": "sha512-1SDS7YIUN1Pg3BmD6TOTjhB7RSBHJRpgIRKx9TpxqyDrJ92sqtZhomDc6UYoMMLQNF2wHFZZVGFjxJhw2VpL+Q==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz",
|
||||
"integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/plugin-content-blog": "3.3.2",
|
||||
"@docusaurus/plugin-content-docs": "3.3.2",
|
||||
"@docusaurus/plugin-content-pages": "3.3.2",
|
||||
"@docusaurus/plugin-debug": "3.3.2",
|
||||
"@docusaurus/plugin-google-analytics": "3.3.2",
|
||||
"@docusaurus/plugin-google-gtag": "3.3.2",
|
||||
"@docusaurus/plugin-google-tag-manager": "3.3.2",
|
||||
"@docusaurus/plugin-sitemap": "3.3.2",
|
||||
"@docusaurus/theme-classic": "3.3.2",
|
||||
"@docusaurus/theme-common": "3.3.2",
|
||||
"@docusaurus/theme-search-algolia": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2"
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/plugin-content-blog": "3.4.0",
|
||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
||||
"@docusaurus/plugin-content-pages": "3.4.0",
|
||||
"@docusaurus/plugin-debug": "3.4.0",
|
||||
"@docusaurus/plugin-google-analytics": "3.4.0",
|
||||
"@docusaurus/plugin-google-gtag": "3.4.0",
|
||||
"@docusaurus/plugin-google-tag-manager": "3.4.0",
|
||||
"@docusaurus/plugin-sitemap": "3.4.0",
|
||||
"@docusaurus/theme-classic": "3.4.0",
|
||||
"@docusaurus/theme-common": "3.4.0",
|
||||
"@docusaurus/theme-search-algolia": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
@@ -2510,22 +2510,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-classic": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.3.2.tgz",
|
||||
"integrity": "sha512-gepHFcsluIkPb4Im9ukkiO4lXrai671wzS3cKQkY9BXQgdVwsdPf/KS0Vs4Xlb0F10fTz+T3gNjkxNEgSN9M0A==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz",
|
||||
"integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/mdx-loader": "3.3.2",
|
||||
"@docusaurus/module-type-aliases": "3.3.2",
|
||||
"@docusaurus/plugin-content-blog": "3.3.2",
|
||||
"@docusaurus/plugin-content-docs": "3.3.2",
|
||||
"@docusaurus/plugin-content-pages": "3.3.2",
|
||||
"@docusaurus/theme-common": "3.3.2",
|
||||
"@docusaurus/theme-translations": "3.3.2",
|
||||
"@docusaurus/types": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/mdx-loader": "3.4.0",
|
||||
"@docusaurus/module-type-aliases": "3.4.0",
|
||||
"@docusaurus/plugin-content-blog": "3.4.0",
|
||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
||||
"@docusaurus/plugin-content-pages": "3.4.0",
|
||||
"@docusaurus/theme-common": "3.4.0",
|
||||
"@docusaurus/theme-translations": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"copy-text-to-clipboard": "^3.2.0",
|
||||
@@ -2549,17 +2549,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-common": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.3.2.tgz",
|
||||
"integrity": "sha512-kXqSaL/sQqo4uAMQ4fHnvRZrH45Xz2OdJ3ABXDS7YVGPSDTBC8cLebFrRR4YF9EowUHto1UC/EIklJZQMG/usA==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz",
|
||||
"integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==",
|
||||
"dependencies": {
|
||||
"@docusaurus/mdx-loader": "3.3.2",
|
||||
"@docusaurus/module-type-aliases": "3.3.2",
|
||||
"@docusaurus/plugin-content-blog": "3.3.2",
|
||||
"@docusaurus/plugin-content-docs": "3.3.2",
|
||||
"@docusaurus/plugin-content-pages": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/mdx-loader": "3.4.0",
|
||||
"@docusaurus/module-type-aliases": "3.4.0",
|
||||
"@docusaurus/plugin-content-blog": "3.4.0",
|
||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
||||
"@docusaurus/plugin-content-pages": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router-config": "*",
|
||||
@@ -2578,18 +2578,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-search-algolia": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.3.2.tgz",
|
||||
"integrity": "sha512-qLkfCl29VNBnF1MWiL9IyOQaHxUvicZp69hISyq/xMsNvFKHFOaOfk9xezYod2Q9xx3xxUh9t/QPigIei2tX4w==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz",
|
||||
"integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==",
|
||||
"dependencies": {
|
||||
"@docsearch/react": "^3.5.2",
|
||||
"@docusaurus/core": "3.3.2",
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/plugin-content-docs": "3.3.2",
|
||||
"@docusaurus/theme-common": "3.3.2",
|
||||
"@docusaurus/theme-translations": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-validation": "3.3.2",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/plugin-content-docs": "3.4.0",
|
||||
"@docusaurus/theme-common": "3.4.0",
|
||||
"@docusaurus/theme-translations": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-validation": "3.4.0",
|
||||
"algoliasearch": "^4.18.0",
|
||||
"algoliasearch-helper": "^3.13.3",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -2608,9 +2608,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/theme-translations": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.3.2.tgz",
|
||||
"integrity": "sha512-bPuiUG7Z8sNpGuTdGnmKl/oIPeTwKr0AXLGu9KaP6+UFfRZiyWbWE87ti97RrevB2ffojEdvchNujparR3jEZQ==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz",
|
||||
"integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==",
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.1",
|
||||
"tslib": "^2.6.0"
|
||||
@@ -2620,15 +2620,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/tsconfig": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.3.2.tgz",
|
||||
"integrity": "sha512-2MQXkLoWqgOSiqFojNEq8iPtFBHGQqd1b/SQMoe+v3GgHmk/L6YTTO/hMcHhWb1hTFmbkei++IajSfD3RlZKvw==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.4.0.tgz",
|
||||
"integrity": "sha512-0qENiJ+TRaeTzcg4olrnh0BQ7eCxTgbYWBnWUeQDc84UYkt/T3pDNnm3SiQkqPb+YQ1qtYFlC0RriAElclo8Dg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@docusaurus/types": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.3.2.tgz",
|
||||
"integrity": "sha512-5p201S7AZhliRxTU7uMKtSsoC8mgPA9bs9b5NQg1IRdRxJfflursXNVsgc3PcMqiUTul/v1s3k3rXXFlRE890w==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz",
|
||||
"integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==",
|
||||
"dependencies": {
|
||||
"@mdx-js/mdx": "^3.0.0",
|
||||
"@types/history": "^4.7.11",
|
||||
@@ -2646,12 +2646,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/utils": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.3.2.tgz",
|
||||
"integrity": "sha512-f4YMnBVymtkSxONv4Y8js3Gez9IgHX+Lcg6YRMOjVbq8sgCcdYK1lf6SObAuz5qB/mxiSK7tW0M9aaiIaUSUJg==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz",
|
||||
"integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
@@ -2668,6 +2668,7 @@
|
||||
"shelljs": "^0.8.5",
|
||||
"tslib": "^2.6.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"utility-types": "^3.10.0",
|
||||
"webpack": "^5.88.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2683,9 +2684,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/utils-common": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.3.2.tgz",
|
||||
"integrity": "sha512-QWFTLEkPYsejJsLStgtmetMFIA3pM8EPexcZ4WZ7b++gO5jGVH7zsipREnCHzk6+eDgeaXfkR6UPaTt86bp8Og==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz",
|
||||
"integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
@@ -2702,15 +2703,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@docusaurus/utils-validation": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.3.2.tgz",
|
||||
"integrity": "sha512-itDgFs5+cbW9REuC7NdXals4V6++KifgVMzoGOOOSIifBQw+8ULhy86u5e1lnptVL0sv8oAjq2alO7I40GR7pA==",
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz",
|
||||
"integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==",
|
||||
"dependencies": {
|
||||
"@docusaurus/logger": "3.3.2",
|
||||
"@docusaurus/utils": "3.3.2",
|
||||
"@docusaurus/utils-common": "3.3.2",
|
||||
"@docusaurus/logger": "3.4.0",
|
||||
"@docusaurus/utils": "3.4.0",
|
||||
"@docusaurus/utils-common": "3.4.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"joi": "^17.9.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"tslib": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3415,9 +3418,9 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz",
|
||||
"integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
||||
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
@@ -3887,9 +3890,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/algoliasearch-helper": {
|
||||
"version": "3.19.0",
|
||||
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.19.0.tgz",
|
||||
"integrity": "sha512-AaSb5DZDMZmDQyIy6lf4aL0OZGgyIdqvLIIvSuVQOIOqfhrYSY7TvotIFI2x0Q3cP3xUpTd7lI1astUC4aXBJw==",
|
||||
"version": "3.21.0",
|
||||
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.21.0.tgz",
|
||||
"integrity": "sha512-hjVOrL15I3Y3K8xG0icwG1/tWE+MocqBrhW6uVBWpU+/kVEMK0BnM2xdssj6mZM61eJ4iRxHR0djEI3ENOpR8w==",
|
||||
"dependencies": {
|
||||
"@algolia/events": "^4.0.1"
|
||||
},
|
||||
@@ -4238,11 +4241,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -6227,9 +6230,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -8145,9 +8148,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-from-markdown": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz",
|
||||
"integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
|
||||
"integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
@@ -8427,9 +8430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-to-hast": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz",
|
||||
"integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==",
|
||||
"version": "13.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
|
||||
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/mdast": "^4.0.0",
|
||||
@@ -12507,9 +12510,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
@@ -12538,9 +12541,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/search-insights": {
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz",
|
||||
"integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==",
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz",
|
||||
"integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
@@ -12898,9 +12901,9 @@
|
||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
|
||||
},
|
||||
"node_modules/sitemap": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz",
|
||||
"integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz",
|
||||
"integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==",
|
||||
"dependencies": {
|
||||
"@types/node": "^17.0.5",
|
||||
"@types/sax": "^1.2.1",
|
||||
@@ -13524,9 +13527,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
+11
-11
@@ -15,20 +15,20 @@
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.3.2",
|
||||
"@docusaurus/preset-classic": "^3.3.2",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"@docusaurus/core": "^3.4.0",
|
||||
"@docusaurus/preset-classic": "^3.4.0",
|
||||
"@mdx-js/react": "^3.0.1",
|
||||
"clsx": "^2.1.1",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.3.2",
|
||||
"@docusaurus/tsconfig": "^3.3.2",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@docusaurus/module-type-aliases": "^3.4.0",
|
||||
"@docusaurus/tsconfig": "^3.4.0",
|
||||
"@docusaurus/types": "^3.4.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"typescript": "~5.2.2"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
const sidebars: SidebarsConfig = {
|
||||
docs: [
|
||||
"intro",
|
||||
"resources",
|
||||
"core-setup",
|
||||
{
|
||||
type: "category",
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
|
||||
<title>Monitor</title>
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<body class="min-h-screen bg-background">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
@@ -639,3 +639,68 @@ const default_registry_config = (
|
||||
return { type, params: "" };
|
||||
}
|
||||
};
|
||||
|
||||
export const SecretSelector = ({
|
||||
keys,
|
||||
onSelect,
|
||||
type,
|
||||
disabled,
|
||||
}: {
|
||||
keys: string[];
|
||||
onSelect: (key: string) => void;
|
||||
type: "Variable" | "Secret";
|
||||
disabled: boolean;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const filtered = filterBySplit(keys, search, (item) => item).sort((a, b) => {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
} else if (a < b) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="secondary" className="flex gap-2" disabled={disabled}>
|
||||
<PlusCircle className="w-4 h-4" />
|
||||
<div>Add {type}</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[300px] max-h-[300px] p-0" align="start">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder={`Search ${type}s`}
|
||||
className="h-9"
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty className="flex justify-evenly items-center pt-2">
|
||||
{`No ${type}s Found`}
|
||||
<SearchX className="w-3 h-3" />
|
||||
</CommandEmpty>
|
||||
|
||||
<CommandGroup>
|
||||
{filtered.map((key) => (
|
||||
<CommandItem
|
||||
key={key}
|
||||
onSelect={() => {
|
||||
onSelect(key);
|
||||
setOpen(false);
|
||||
}}
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
>
|
||||
<div className="p-1">{key}</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -32,7 +32,7 @@ export const ExportButton = ({
|
||||
<FileDown className="w-4 h-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="min-w-[600px]">
|
||||
<DialogContent className="w-[900px] max-w-[95vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Export to Toml</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { BuilderComponents } from "./resources/builder";
|
||||
import { AlerterComponents } from "./resources/alerter";
|
||||
import { ServerTemplateComponents } from "./resources/server-template";
|
||||
import { Badge } from "@ui/badge";
|
||||
import { ResourceSyncComponents } from "./resources/resource-sync";
|
||||
|
||||
export const OmniSearch = ({
|
||||
className,
|
||||
@@ -43,7 +44,10 @@ export const OmniSearch = ({
|
||||
<Search className="w-4 h-4" />{" "}
|
||||
<span className="text-muted-foreground hidden md:flex">Search</span>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-muted-foreground hidden md:inline-flex">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-muted-foreground hidden md:inline-flex"
|
||||
>
|
||||
shift + s
|
||||
</Badge>
|
||||
</Button>
|
||||
@@ -117,6 +121,7 @@ const useOmniItems = (
|
||||
const builders = useRead("ListBuilders", {}).data;
|
||||
const alerters = useRead("ListAlerters", {}).data;
|
||||
const templates = useRead("ListServerTemplates", {}).data;
|
||||
const syncs = useRead("ListResourceSyncs", {}).data;
|
||||
const searchTerms = search
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
@@ -169,6 +174,11 @@ const useOmniItems = (
|
||||
icon: <ServerTemplateComponents.Icon />,
|
||||
onSelect: () => nav("/server-templates"),
|
||||
},
|
||||
{
|
||||
label: "Syncs",
|
||||
icon: <ResourceSyncComponents.Icon />,
|
||||
onSelect: () => nav("/resource-syncs"),
|
||||
},
|
||||
(user?.admin && {
|
||||
label: "Users",
|
||||
icon: <User className="w-4 h-4" />,
|
||||
@@ -282,7 +292,7 @@ const useOmniItems = (
|
||||
)
|
||||
.map((builder) => ({
|
||||
label: builder.name,
|
||||
icon: <ProcedureComponents.Icon id={builder.id} />,
|
||||
icon: <BuilderComponents.Icon id={builder.id} />,
|
||||
onSelect: () => nav(`/builders/${builder.id}`),
|
||||
})) || [],
|
||||
|
||||
@@ -299,7 +309,7 @@ const useOmniItems = (
|
||||
)
|
||||
.map((alerter) => ({
|
||||
label: alerter.name,
|
||||
icon: <ProcedureComponents.Icon id={alerter.id} />,
|
||||
icon: <AlerterComponents.Icon id={alerter.id} />,
|
||||
onSelect: () => nav(`/alerters/${alerter.id}`),
|
||||
})) || [],
|
||||
|
||||
@@ -316,10 +326,39 @@ const useOmniItems = (
|
||||
)
|
||||
.map((template) => ({
|
||||
label: template.name,
|
||||
icon: <ProcedureComponents.Icon id={template.id} />,
|
||||
onSelect: () => nav(`/templates/${template.id}`),
|
||||
icon: <ServerTemplateComponents.Icon id={template.id} />,
|
||||
onSelect: () => nav(`/server-templates/${template.id}`),
|
||||
})) || [],
|
||||
|
||||
Syncs:
|
||||
syncs
|
||||
?.filter(
|
||||
(item) =>
|
||||
searchTerms.length === 0 ||
|
||||
searchTerms.every(
|
||||
(term) =>
|
||||
item.name.toLowerCase().includes(term) ||
|
||||
"sync".includes(term)
|
||||
)
|
||||
)
|
||||
.map((template) => ({
|
||||
label: template.name,
|
||||
icon: <ResourceSyncComponents.Icon id={template.id} />,
|
||||
onSelect: () => nav(`/resource-syncs/${template.id}`),
|
||||
})) || [],
|
||||
}),
|
||||
[user, servers, deployments, builds, repos, procedures, search]
|
||||
[
|
||||
user,
|
||||
servers,
|
||||
deployments,
|
||||
builds,
|
||||
repos,
|
||||
procedures,
|
||||
alerters,
|
||||
builders,
|
||||
templates,
|
||||
syncs,
|
||||
search,
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,9 +38,10 @@ export const RunBuild = ({ id }: { id: string }) => {
|
||||
(u) => u.operation === Types.Operation.CancelBuild
|
||||
);
|
||||
const cancelDisabled =
|
||||
cancelPending || (latestCancel && latestBuild)
|
||||
cancelPending ||
|
||||
(latestCancel && latestBuild
|
||||
? latestCancel!.start_ts > latestBuild!.start_ts
|
||||
: false;
|
||||
: false);
|
||||
|
||||
if (building) {
|
||||
return (
|
||||
|
||||
@@ -5,17 +5,17 @@ import {
|
||||
ConfigItem,
|
||||
ImageRegistryConfig,
|
||||
InputList,
|
||||
SecretSelector,
|
||||
SystemCommand,
|
||||
} from "@components/config/util";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { env_to_text, text_to_env } from "@lib/utils";
|
||||
import { env_to_text } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { Button } from "@ui/button";
|
||||
import { Textarea } from "@ui/textarea";
|
||||
import { PlusCircle } from "lucide-react";
|
||||
import { ReactNode, RefObject, createRef, useEffect, useState } from "react";
|
||||
import { ReactNode, RefObject, createRef, useState } from "react";
|
||||
import { CopyGithubWebhook, LabelsConfig, ResourceSelector } from "../common";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
|
||||
|
||||
export const BuildConfig = ({
|
||||
id,
|
||||
@@ -209,13 +209,15 @@ export const BuildConfig = ({
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
set((update) => ({
|
||||
...update,
|
||||
labels: [
|
||||
...(update.labels ?? config.labels ?? []),
|
||||
{ variable: "", value: "" },
|
||||
],
|
||||
}))
|
||||
set((update) => {
|
||||
return {
|
||||
...update,
|
||||
labels: [
|
||||
...(update.labels ?? config.labels ?? []),
|
||||
{ variable: "", value: "" },
|
||||
] as Types.EnvironmentVar[],
|
||||
};
|
||||
})
|
||||
}
|
||||
className="flex items-center gap-2 w-[200px]"
|
||||
>
|
||||
@@ -225,7 +227,11 @@ export const BuildConfig = ({
|
||||
),
|
||||
components: {
|
||||
labels: (l, set) => (
|
||||
<LabelsConfig labels={l ?? []} set={set} disabled={disabled} />
|
||||
<LabelsConfig
|
||||
labels={(l as Types.EnvironmentVar[]) ?? []}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -257,9 +263,13 @@ export const BuildConfig = ({
|
||||
{
|
||||
label: "Build Args",
|
||||
components: {
|
||||
build_args: (vars, set) => (
|
||||
<BuildArgs vars={vars ?? []} set={set} disabled={disabled} />
|
||||
),
|
||||
build_args: (vars, set) => {
|
||||
const args =
|
||||
typeof vars === "object" ? env_to_text(vars) : vars;
|
||||
return (
|
||||
<BuildArgs args={args ?? ""} set={set} disabled={disabled} />
|
||||
);
|
||||
},
|
||||
skip_secret_interp: true,
|
||||
},
|
||||
},
|
||||
@@ -270,44 +280,16 @@ export const BuildConfig = ({
|
||||
};
|
||||
|
||||
const BuildArgs = ({
|
||||
vars,
|
||||
args,
|
||||
set,
|
||||
disabled,
|
||||
}: {
|
||||
vars: Types.EnvironmentVar[];
|
||||
args: string;
|
||||
set: (input: Partial<Types.BuildConfig>) => void;
|
||||
disabled: boolean;
|
||||
}) => {
|
||||
const ref = createRef<HTMLTextAreaElement>();
|
||||
const [args, setArgs] = useState<string>();
|
||||
useEffect(() => setArgs(env_to_text(vars)), [vars]);
|
||||
|
||||
const update = () => {
|
||||
if (!args) return;
|
||||
const parsed = text_to_env(args);
|
||||
|
||||
// Diff the vars from old to new
|
||||
for (const [v, i] of vars.map(
|
||||
(v, i) => [v, i] as [Types.EnvironmentVar, number]
|
||||
)) {
|
||||
const _v = parsed[i];
|
||||
if (!_v || v.value !== _v.value || v.variable !== _v.variable) {
|
||||
set({ build_args: parsed });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Diff the vars from new to old
|
||||
for (const [v, i] of parsed.map(
|
||||
(v, i) => [v, i] as [Types.EnvironmentVar, number]
|
||||
)) {
|
||||
const _v = vars[i];
|
||||
if (!_v || v.value !== _v.value || v.variable !== _v.variable) {
|
||||
set({ build_args: parsed });
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
const setArgs = (build_args: string) => set({ build_args });
|
||||
|
||||
return (
|
||||
<ConfigItem className="flex-col gap-4 items-start">
|
||||
@@ -318,7 +300,6 @@ const BuildArgs = ({
|
||||
placeholder="VARIABLE=value"
|
||||
value={args}
|
||||
onChange={(e) => setArgs(e.target.value)}
|
||||
onBlur={update}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</ConfigItem>
|
||||
@@ -343,103 +324,36 @@ const Secrets = ({
|
||||
|
||||
if (variables.length === 0 && secrets.length === 0) return;
|
||||
|
||||
if (variables.length === 0) {
|
||||
// ONLY SECRETS
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-muted-foreground">Secrets</h2>
|
||||
<div className="flex gap-4 items-center flex-wrap w-full">
|
||||
{secrets.map((secret) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={secret}
|
||||
onClick={() =>
|
||||
setArgs(
|
||||
_args.slice(0, argsRef.current?.selectionStart) +
|
||||
`[[${secret}]]` +
|
||||
_args.slice(argsRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{secret}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (secrets.length === 0) {
|
||||
// ONLY VARIABLES
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-muted-foreground">Variables</h2>
|
||||
<div className="flex gap-4 items-center flex-wrap w-full">
|
||||
{variables.map(({ name }) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={name}
|
||||
onClick={() =>
|
||||
setArgs(
|
||||
_args.slice(0, argsRef.current?.selectionStart) +
|
||||
`[[${name}]]` +
|
||||
_args.slice(argsRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs className="w-full" defaultValue="Variables">
|
||||
<TabsList>
|
||||
<TabsTrigger value="Variables">Variables</TabsTrigger>
|
||||
<TabsTrigger value="Secrets">Secrets</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="Variables">
|
||||
<div className="flex gap-4 items-center w-full flex-wrap pt-1">
|
||||
{variables.map(({ name }) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={name}
|
||||
onClick={() =>
|
||||
setArgs(
|
||||
_args.slice(0, argsRef.current?.selectionStart) +
|
||||
`[[${name}]]` +
|
||||
_args.slice(argsRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="Secrets">
|
||||
<div className="flex gap-4 items-center w-full flex-wrap pt-1">
|
||||
{secrets.map((secret) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={secret}
|
||||
onClick={() =>
|
||||
setArgs(
|
||||
_args.slice(0, argsRef.current?.selectionStart) +
|
||||
`[[${secret}]]` +
|
||||
_args.slice(argsRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{secret}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className="flex items-center gap-2">
|
||||
{variables.length > 0 && (
|
||||
<SecretSelector
|
||||
type="Variable"
|
||||
keys={variables.map((v) => v.name)}
|
||||
onSelect={(variable) =>
|
||||
setArgs(
|
||||
_args.slice(0, argsRef.current?.selectionStart) +
|
||||
`[[${variable}]]` +
|
||||
_args.slice(argsRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
{secrets.length > 0 && (
|
||||
<SecretSelector
|
||||
type="Secret"
|
||||
keys={secrets}
|
||||
onSelect={(secret) =>
|
||||
setArgs(
|
||||
_args.slice(0, argsRef.current?.selectionStart) +
|
||||
`[[${secret}]]` +
|
||||
_args.slice(argsRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { RunBuild } from "./actions";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
build_state_intention,
|
||||
fill_color_class_by_intention,
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { Card, CardHeader } from "@ui/card";
|
||||
import { cn } from "@lib/utils";
|
||||
@@ -25,7 +25,7 @@ const useBuild = (id?: string) =>
|
||||
|
||||
const BuildIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
const state = useBuild(id)?.info.state;
|
||||
const color = fill_color_class_by_intention(build_state_intention(state));
|
||||
const color = stroke_color_class_by_intention(build_state_intention(state));
|
||||
return <Hammer className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
|
||||
target: { type: "Builder", id },
|
||||
}).data;
|
||||
const config = useRead("GetBuilder", { builder: id }).data?.config
|
||||
.params as Types.AwsBuilderConfig;
|
||||
?.params as Types.AwsBuilderConfig;
|
||||
const global_disabled =
|
||||
useRead("GetCoreInfo", {}).data?.ui_write_disabled ?? false;
|
||||
const [update, set] = useState<Partial<Types.AwsBuilderConfig>>({});
|
||||
@@ -61,7 +61,7 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
set(update => ({
|
||||
set((update) => ({
|
||||
...update,
|
||||
security_group_ids: [
|
||||
...(update.security_group_ids ??
|
||||
@@ -97,7 +97,7 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
set(update => ({
|
||||
set((update) => ({
|
||||
...update,
|
||||
github_accounts: [
|
||||
...(update.github_accounts ??
|
||||
|
||||
@@ -402,4 +402,4 @@ export const ServerSelector = ({
|
||||
align={align}
|
||||
/>
|
||||
</ConfigItem>
|
||||
);
|
||||
);
|
||||
@@ -23,8 +23,8 @@ export const DeployContainer = ({ id }: DeploymentId) => {
|
||||
const [signal, setSignal] = useState<Types.TerminationSignal>();
|
||||
|
||||
useEffect(
|
||||
() => setSignal(deployment?.config.termination_signal),
|
||||
[deployment?.config.termination_signal]
|
||||
() => setSignal(deployment?.config?.termination_signal),
|
||||
[deployment?.config?.termination_signal]
|
||||
);
|
||||
|
||||
const { mutate: deploy, isPending } = useExecute("Deploy");
|
||||
@@ -60,9 +60,9 @@ export const DeployContainer = ({ id }: DeploymentId) => {
|
||||
disabled={pending}
|
||||
loading={pending}
|
||||
additional={
|
||||
deployed && deployment.config.term_signal_labels.length > 1 ? (
|
||||
deployed && deployment.config!.term_signal_labels.length > 1 ? (
|
||||
<TermSignalSelector
|
||||
signals={deployment.config.term_signal_labels}
|
||||
signals={deployment.config!.term_signal_labels}
|
||||
signal={signal}
|
||||
setSignal={setSignal}
|
||||
/>
|
||||
@@ -113,8 +113,8 @@ const StopContainer = ({ id }: DeploymentId) => {
|
||||
const [signal, setSignal] = useState<Types.TerminationSignal>();
|
||||
|
||||
useEffect(
|
||||
() => setSignal(deployment?.config.termination_signal),
|
||||
[deployment?.config.termination_signal]
|
||||
() => setSignal(deployment?.config?.termination_signal),
|
||||
[deployment?.config?.termination_signal]
|
||||
);
|
||||
|
||||
const { mutate, isPending } = useExecute("StopContainer");
|
||||
@@ -138,9 +138,9 @@ const StopContainer = ({ id }: DeploymentId) => {
|
||||
disabled={pending}
|
||||
loading={pending}
|
||||
additional={
|
||||
deployment.config.term_signal_labels.length > 1 ? (
|
||||
deployment.config!.term_signal_labels.length > 1 ? (
|
||||
<TermSignalSelector
|
||||
signals={deployment.config.term_signal_labels}
|
||||
signals={deployment.config!.term_signal_labels}
|
||||
signal={signal}
|
||||
setSignal={setSignal}
|
||||
/>
|
||||
@@ -181,8 +181,8 @@ export const RemoveContainer = ({ id }: DeploymentId) => {
|
||||
const [signal, setSignal] = useState<Types.TerminationSignal>();
|
||||
|
||||
useEffect(
|
||||
() => setSignal(deployment?.config.termination_signal),
|
||||
[deployment?.config.termination_signal]
|
||||
() => setSignal(deployment?.config?.termination_signal),
|
||||
[deployment?.config?.termination_signal]
|
||||
);
|
||||
|
||||
const { mutate, isPending } = useExecute("RemoveContainer");
|
||||
@@ -212,9 +212,9 @@ export const RemoveContainer = ({ id }: DeploymentId) => {
|
||||
disabled={pending}
|
||||
loading={pending}
|
||||
additional={
|
||||
deployment.config.term_signal_labels.length > 1 ? (
|
||||
deployment.config!.term_signal_labels.length > 1 ? (
|
||||
<TermSignalSelector
|
||||
signals={deployment.config.term_signal_labels}
|
||||
signals={deployment.config!.term_signal_labels}
|
||||
signal={signal}
|
||||
setSignal={setSignal}
|
||||
/>
|
||||
|
||||
@@ -1,57 +1,26 @@
|
||||
import { ConfigItem } from "@components/config/util";
|
||||
import { ConfigItem, SecretSelector } from "@components/config/util";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { env_to_text, text_to_env } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { Button } from "@ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
|
||||
import { Textarea } from "@ui/textarea";
|
||||
import { RefObject, createRef, useEffect, useState } from "react";
|
||||
import { RefObject, createRef } from "react";
|
||||
|
||||
export const EnvVars = ({
|
||||
vars,
|
||||
env,
|
||||
set,
|
||||
disabled,
|
||||
server,
|
||||
}: {
|
||||
vars: Types.EnvironmentVar[];
|
||||
env: string;
|
||||
set: (input: Partial<Types.DeploymentConfig>) => void;
|
||||
disabled: boolean;
|
||||
/// eg server id
|
||||
server?: string;
|
||||
}) => {
|
||||
const ref = createRef<HTMLTextAreaElement>();
|
||||
const [env, setEnv] = useState<string>();
|
||||
useEffect(() => setEnv(env_to_text(vars)), [vars]);
|
||||
|
||||
const update = () => {
|
||||
if (!env) return;
|
||||
const parsed = text_to_env(env);
|
||||
|
||||
// Diff the vars from old to new
|
||||
for (const [v, i] of vars.map(
|
||||
(v, i) => [v, i] as [Types.EnvironmentVar, number]
|
||||
)) {
|
||||
const _v = parsed[i];
|
||||
if (!_v || v.value !== _v.value || v.variable !== _v.variable) {
|
||||
set({ environment: parsed });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Diff the vars from new to old
|
||||
for (const [v, i] of parsed.map(
|
||||
(v, i) => [v, i] as [Types.EnvironmentVar, number]
|
||||
)) {
|
||||
const _v = vars[i];
|
||||
if (!_v || v.value !== _v.value || v.variable !== _v.variable) {
|
||||
set({ environment: parsed });
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
const setEnv = (environment: string) => set({ environment });
|
||||
|
||||
return (
|
||||
<ConfigItem className="flex-col gap-4 items-start">
|
||||
<ConfigItem className="flex-col gap-2 items-start">
|
||||
{!disabled && server && (
|
||||
<Secrets server={server} env={env} setEnv={setEnv} envRef={ref} />
|
||||
)}
|
||||
@@ -61,7 +30,6 @@ export const EnvVars = ({
|
||||
placeholder="VARIABLE=value"
|
||||
value={env}
|
||||
onChange={(e) => setEnv(e.target.value)}
|
||||
onBlur={update}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</ConfigItem>
|
||||
@@ -95,102 +63,36 @@ const Secrets = ({
|
||||
|
||||
if (variables.length === 0 && secrets.length === 0) return;
|
||||
|
||||
if (variables.length === 0) {
|
||||
// ONLY SECRETS
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-muted-foreground">Secrets</h2>
|
||||
<div className="flex gap-4 items-center flex-wrap w-full">
|
||||
{secrets.map((secret) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={secret}
|
||||
onClick={() =>
|
||||
setEnv(
|
||||
_env.slice(0, envRef.current?.selectionStart) +
|
||||
`[[${secret}]]` +
|
||||
_env.slice(envRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{secret}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (secrets.length === 0) {
|
||||
// ONLY VARIABLES
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-muted-foreground">Variables</h2>
|
||||
<div className="flex gap-4 items-center flex-wrap w-full">
|
||||
{variables.map(({ name }) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={name}
|
||||
onClick={() =>
|
||||
setEnv(
|
||||
_env.slice(0, envRef.current?.selectionStart) +
|
||||
`[[${name}]]` +
|
||||
_env.slice(envRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs className="w-full" defaultValue="Variables">
|
||||
<TabsList>
|
||||
<TabsTrigger value="Variables">Variables</TabsTrigger>
|
||||
<TabsTrigger value="Secrets">Secrets</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="Variables">
|
||||
<div className="flex gap-4 items-center w-full flex-wrap pt-1">
|
||||
{variables.map(({ name }) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={name}
|
||||
onClick={() =>
|
||||
setEnv(
|
||||
_env.slice(0, envRef.current?.selectionStart) +
|
||||
`[[${name}]]` +
|
||||
_env.slice(envRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="Secrets">
|
||||
<div className="flex gap-4 items-center w-full flex-wrap pt-1">
|
||||
{secrets.map((secret) => (
|
||||
<Button
|
||||
variant="secondary"
|
||||
key={secret}
|
||||
onClick={() =>
|
||||
setEnv(
|
||||
_env.slice(0, envRef.current?.selectionStart) +
|
||||
`[[${secret}]]` +
|
||||
_env.slice(envRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
>
|
||||
{secret}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className="flex items-center gap-2">
|
||||
{variables.length > 0 && (
|
||||
<SecretSelector
|
||||
type="Variable"
|
||||
keys={variables.map((v) => v.name)}
|
||||
onSelect={(variable) =>
|
||||
setEnv(
|
||||
_env.slice(0, envRef.current?.selectionStart) +
|
||||
`[[${variable}]]` +
|
||||
_env.slice(envRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
{secrets.length > 0 && (
|
||||
<SecretSelector
|
||||
type="Secret"
|
||||
keys={secrets}
|
||||
onSelect={(secret) =>
|
||||
setEnv(
|
||||
_env.slice(0, envRef.current?.selectionStart) +
|
||||
`[[${secret}]]` +
|
||||
_env.slice(envRef.current?.selectionStart, undefined)
|
||||
)
|
||||
}
|
||||
disabled={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import { LabelsConfig, ServerSelector } from "@components/resources/common";
|
||||
import { TextUpdateMenu } from "@components/util";
|
||||
import { Button } from "@ui/button";
|
||||
import { PlusCircle } from "lucide-react";
|
||||
import { env_to_text } from "@lib/utils";
|
||||
|
||||
export const DeploymentConfig = ({
|
||||
id,
|
||||
@@ -90,7 +91,7 @@ export const DeploymentConfig = ({
|
||||
(config.image?.params as any)?.build_id;
|
||||
const build_registry_type = useRead("GetBuild", {
|
||||
build: build_id!,
|
||||
}).data?.config.image_registry?.type;
|
||||
}).data?.config?.image_registry?.type;
|
||||
const server_id = update.server_id ?? config.server_id;
|
||||
return (
|
||||
<ImageRegistryConfig
|
||||
@@ -240,7 +241,7 @@ export const DeploymentConfig = ({
|
||||
labels: [
|
||||
...(update.labels ?? config.labels ?? []),
|
||||
{ variable: "", value: "" },
|
||||
],
|
||||
] as Types.EnvironmentVar[],
|
||||
})
|
||||
}
|
||||
className="flex items-center gap-2 w-[200px]"
|
||||
@@ -251,7 +252,11 @@ export const DeploymentConfig = ({
|
||||
),
|
||||
components: {
|
||||
labels: (l, set) => (
|
||||
<LabelsConfig labels={l ?? []} set={set} disabled={disabled} />
|
||||
<LabelsConfig
|
||||
labels={(l as Types.EnvironmentVar[]) ?? []}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -268,14 +273,17 @@ export const DeploymentConfig = ({
|
||||
{
|
||||
label: "Environment",
|
||||
components: {
|
||||
environment: (vars, set) => (
|
||||
<EnvVars
|
||||
vars={vars ?? []}
|
||||
set={set}
|
||||
server={update.server_id || config.server_id}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
environment: (vars, set) => {
|
||||
const env = typeof vars === "object" ? env_to_text(vars) : vars;
|
||||
return (
|
||||
<EnvVars
|
||||
env={env ?? ""}
|
||||
set={set}
|
||||
server={update.server_id || config.server_id}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
skip_secret_interp: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ import { snake_case_to_upper_space_case } from "@lib/formatting";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
deployment_state_intention,
|
||||
fill_color_class_by_intention,
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { DeploymentTable } from "./table";
|
||||
import { DeploymentsChart } from "./dashboard";
|
||||
@@ -92,7 +92,7 @@ const ConfigOrLog = ({ id }: { id: string }) => {
|
||||
|
||||
const DeploymentIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
const state = useDeployment(id)?.info.state;
|
||||
const color = fill_color_class_by_intention(
|
||||
const color = stroke_color_class_by_intention(
|
||||
deployment_state_intention(state)
|
||||
);
|
||||
return <Rocket className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
|
||||
@@ -2,12 +2,7 @@ import { Section } from "@components/layouts";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import { Button } from "@ui/button";
|
||||
import {
|
||||
RefreshCw,
|
||||
ChevronDown,
|
||||
X,
|
||||
AlertOctagon,
|
||||
} from "lucide-react";
|
||||
import { RefreshCw, ChevronDown, X, AlertOctagon } from "lucide-react";
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { useDeployment } from ".";
|
||||
import {
|
||||
@@ -22,8 +17,15 @@ import { Input } from "@ui/input";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { logToHtml } from "@lib/utils";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@ui/toggle-group";
|
||||
import { Switch } from "@ui/switch";
|
||||
|
||||
export const DeploymentLogs = ({ id, titleOther }: { id: string; titleOther: ReactNode }) => {
|
||||
export const DeploymentLogs = ({
|
||||
id,
|
||||
titleOther,
|
||||
}: {
|
||||
id: string;
|
||||
titleOther: ReactNode;
|
||||
}) => {
|
||||
const state = useDeployment(id)?.info.state;
|
||||
if (
|
||||
state === undefined ||
|
||||
@@ -35,11 +37,18 @@ export const DeploymentLogs = ({ id, titleOther }: { id: string; titleOther: Rea
|
||||
return <DeploymentLogsInner id={id} titleOther={titleOther} />;
|
||||
};
|
||||
|
||||
const DeploymentLogsInner = ({ id, titleOther }: { id: string; titleOther: ReactNode }) => {
|
||||
const DeploymentLogsInner = ({
|
||||
id,
|
||||
titleOther,
|
||||
}: {
|
||||
id: string;
|
||||
titleOther: ReactNode;
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const [stream, setStream] = useState("stdout");
|
||||
const [tail, set] = useState("100");
|
||||
const [terms, setTerms] = useState<string[]>([]);
|
||||
const [invert, setInvert] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const addTerm = () => {
|
||||
@@ -59,14 +68,18 @@ const DeploymentLogsInner = ({ id, titleOther }: { id: string; titleOther: React
|
||||
};
|
||||
|
||||
const { Log, refetch, stderr } = terms.length
|
||||
? SearchLogs(id, terms)
|
||||
? SearchLogs(id, terms, invert)
|
||||
: NoSearchLogs(id, tail, stream);
|
||||
|
||||
return (
|
||||
<Section
|
||||
titleOther={titleOther}
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-muted-foreground">Invert Search </div>
|
||||
<Switch checked={invert} onCheckedChange={setInvert} />
|
||||
</div>
|
||||
{terms.map((term, index) => (
|
||||
<Button
|
||||
key={term}
|
||||
@@ -87,7 +100,7 @@ const DeploymentLogsInner = ({ id, titleOther }: { id: string; titleOther: React
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") addTerm();
|
||||
}}
|
||||
className="w-[300px]"
|
||||
className="w-[240px]"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -140,11 +153,12 @@ const NoSearchLogs = (id: string, tail: string, stream: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
const SearchLogs = (id: string, terms: string[]) => {
|
||||
const SearchLogs = (id: string, terms: string[], invert: boolean) => {
|
||||
const { data: log, refetch } = useRead("SearchLog", {
|
||||
deployment: id,
|
||||
terms,
|
||||
combinator: Types.SearchCombinator.And,
|
||||
invert,
|
||||
});
|
||||
return {
|
||||
Log: (
|
||||
@@ -182,7 +196,11 @@ const Log = ({
|
||||
className="-scroll-mt-24 pb-[20vh]"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="secondary" className="absolute top-4 right-4" onClick={scroll}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="absolute top-4 right-4"
|
||||
onClick={scroll}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -62,7 +62,7 @@ const ProcedureConfigInner = ({
|
||||
const global_disabled =
|
||||
useRead("GetCoreInfo", {}).data?.ui_write_disabled ?? false;
|
||||
const { mutateAsync } = useWrite("UpdateProcedure");
|
||||
const stages = config.stages || procedure.config.stages || [];
|
||||
const stages = config.stages || procedure.config?.stages || [];
|
||||
|
||||
const disabled = global_disabled || perms !== Types.PermissionLevel.Write;
|
||||
|
||||
@@ -214,7 +214,7 @@ const ProcedureConfigInner = ({
|
||||
<div className="text-muted-foreground">Enabled:</div>
|
||||
<Switch
|
||||
checked={
|
||||
config.webhook_enabled ?? procedure.config.webhook_enabled
|
||||
config.webhook_enabled ?? procedure.config?.webhook_enabled
|
||||
}
|
||||
onCheckedChange={(webhook_enabled) =>
|
||||
setConfig({ ...config, webhook_enabled })
|
||||
@@ -346,30 +346,6 @@ const Stage = ({
|
||||
</Button>
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
header: "Enabled",
|
||||
cell: ({
|
||||
row: {
|
||||
original: { enabled },
|
||||
index,
|
||||
},
|
||||
}) => {
|
||||
return (
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onClick={() =>
|
||||
setStage({
|
||||
...stage,
|
||||
executions: stage.executions!.map((item, i) =>
|
||||
i === index ? { ...item, enabled: !enabled } : item
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Execution",
|
||||
cell: ({ row: { original, index } }) => (
|
||||
@@ -431,7 +407,7 @@ const Stage = ({
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Modify",
|
||||
header: "Add / Remove",
|
||||
cell: ({ row: { index } }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
@@ -467,6 +443,30 @@ const Stage = ({
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Enabled",
|
||||
cell: ({
|
||||
row: {
|
||||
original: { enabled },
|
||||
index,
|
||||
},
|
||||
}) => {
|
||||
return (
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onClick={() =>
|
||||
setStage({
|
||||
...stage,
|
||||
executions: stage.executions!.map((item, i) =>
|
||||
i === index ? { ...item, enabled: !enabled } : item
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
@@ -595,7 +595,6 @@ const TARGET_COMPONENTS: ExecutionConfigs = {
|
||||
Deploy: {
|
||||
params: { deployment: "" },
|
||||
Component: ({ params, setParams, disabled }) => {
|
||||
console.log(params.deployment);
|
||||
return (
|
||||
<ResourceSelector
|
||||
type="Deployment"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DeleteResource, NewResource } from "../common";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
procedure_state_intention,
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
import { ProcedureDashboard } from "./dashboard";
|
||||
@@ -17,6 +18,12 @@ import { Types } from "@monitor/client";
|
||||
const useProcedure = (id?: string) =>
|
||||
useRead("ListProcedures", {}).data?.find((d) => d.id === id);
|
||||
|
||||
const ProcedureIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
const state = useProcedure(id)?.info.state;
|
||||
const color = stroke_color_class_by_intention(procedure_state_intention(state));
|
||||
return <Route className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
};
|
||||
|
||||
export const ProcedureComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useProcedure(id),
|
||||
|
||||
@@ -28,8 +35,8 @@ export const ProcedureComponents: RequiredResourceComponents = {
|
||||
<ProcedureTable procedures={resources as Types.ProcedureListItem[]} />
|
||||
),
|
||||
|
||||
Icon: () => <Route className="w-4" />,
|
||||
BigIcon: () => <Route className="w-8" />,
|
||||
Icon: ({ id }) => <ProcedureIcon id={id} size={4} />,
|
||||
BigIcon: ({ id }) => <ProcedureIcon id={id} size={8} />,
|
||||
|
||||
Status: {
|
||||
State: ({ id }) => {
|
||||
|
||||
@@ -52,6 +52,9 @@ export const RepoConfig = ({ id }: { id: string }) => {
|
||||
repo: { placeholder: "Enter repo" },
|
||||
branch: { placeholder: "Enter branch" },
|
||||
commit: { placeholder: "Enter specific commit hash. Optional." },
|
||||
path: {
|
||||
placeholder: "Enter a specific clone path. Optional.",
|
||||
},
|
||||
github_account: (value, set) => {
|
||||
const server_id = update.server_id || config.server_id;
|
||||
return (
|
||||
|
||||
@@ -8,8 +8,8 @@ import { DeleteResource, NewResource, ResourceLink } from "../common";
|
||||
import { RepoTable } from "./table";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
fill_color_class_by_intention,
|
||||
repo_state_intention,
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
|
||||
@@ -24,7 +24,7 @@ export const useRepo = (id?: string) =>
|
||||
|
||||
const RepoIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
const state = useRepo(id)?.info.state;
|
||||
const color = fill_color_class_by_intention(repo_state_intention(state));
|
||||
const color = stroke_color_class_by_intention(repo_state_intention(state));
|
||||
return <GitBranch className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PendingOrConfig } from "./pending-or-config";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
resource_sync_state_intention,
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { cn } from "@lib/utils";
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
|
||||
@@ -21,6 +22,14 @@ const useResourceSync = (id?: string) =>
|
||||
(d) => d.id === id
|
||||
);
|
||||
|
||||
const ResourceSyncIcon = ({ id, size }: { id?: string; size: number }) => {
|
||||
const state = useResourceSync(id)?.info.state;
|
||||
const color = stroke_color_class_by_intention(
|
||||
resource_sync_state_intention(state)
|
||||
);
|
||||
return <FolderSync className={cn(`w-${size} h-${size}`, state && color)} />;
|
||||
};
|
||||
|
||||
export const ResourceSyncComponents: RequiredResourceComponents = {
|
||||
list_item: (id) => useResourceSync(id),
|
||||
|
||||
@@ -49,8 +58,8 @@ export const ResourceSyncComponents: RequiredResourceComponents = {
|
||||
<ResourceSyncTable syncs={resources as Types.ResourceSyncListItem[]} />
|
||||
),
|
||||
|
||||
Icon: () => <FolderSync className="w-4 h-4" />,
|
||||
BigIcon: () => <FolderSync className="w-8 h-8" />,
|
||||
Icon: ({ id }) => <ResourceSyncIcon id={id} size={4} />,
|
||||
BigIcon: ({ id }) => <ResourceSyncIcon id={id} size={8} />,
|
||||
|
||||
Status: {
|
||||
State: ({ id }) => {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const AwsServerTemplateConfig = ({
|
||||
disabled: boolean;
|
||||
}) => {
|
||||
const config = useRead("GetServerTemplate", { server_template: id }).data
|
||||
?.config.params as Types.AwsServerTemplateConfig;
|
||||
?.config?.params as Types.AwsServerTemplateConfig;
|
||||
const [update, set] = useState<Partial<Types.AwsServerTemplateConfig>>({});
|
||||
const { mutateAsync } = useWrite("UpdateServerTemplate");
|
||||
if (!config) return null;
|
||||
|
||||
@@ -41,7 +41,7 @@ export const HetznerServerTemplateConfig = ({
|
||||
disabled: boolean;
|
||||
}) => {
|
||||
const config = useRead("GetServerTemplate", { server_template: id }).data
|
||||
?.config.params as Types.HetznerServerTemplateConfig;
|
||||
?.config?.params as Types.HetznerServerTemplateConfig;
|
||||
const [update, set] = useState<Partial<Types.HetznerServerTemplateConfig>>(
|
||||
{}
|
||||
);
|
||||
|
||||
@@ -16,8 +16,8 @@ import { Section } from "@components/layouts";
|
||||
import { RenameServer } from "./actions";
|
||||
import {
|
||||
bg_color_class_by_intention,
|
||||
fill_color_class_by_intention,
|
||||
server_state_intention,
|
||||
stroke_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
import { ServerConfig } from "./config";
|
||||
import { DeploymentTable } from "../deployment/table";
|
||||
@@ -44,7 +44,7 @@ const Icon = ({ id, size }: { id?: string; size: number }) => {
|
||||
<Server
|
||||
className={cn(
|
||||
`w-${size} h-${size}`,
|
||||
id && fill_color_class_by_intention(server_state_intention(state))
|
||||
state && stroke_color_class_by_intention(server_state_intention(state))
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -258,15 +258,6 @@ export const AddTags = ({ target }: { target: TargetExcludingSystem }) => {
|
||||
className="h-9"
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
// check that no tags still match
|
||||
all_tags?.every((tag) => !tag.name.includes(search))
|
||||
) {
|
||||
create_tag();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty className="m-1">
|
||||
|
||||
+25
-18
@@ -25,31 +25,38 @@ export const hex_color_by_intention = (intention: ColorIntention) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const color_class_by_intention = (intention: ColorIntention) => {
|
||||
export const fill_color_class_by_intention = (intention: ColorIntention) => {
|
||||
switch (intention) {
|
||||
case "Good":
|
||||
return "green-500";
|
||||
return "text-green-400 dark:text-green-700";
|
||||
case "Neutral":
|
||||
return "blue-500";
|
||||
return "text-blue-400 dark:text-blue-700";
|
||||
case "Warning":
|
||||
return "orange-500";
|
||||
return "text-orange-400 dark:text-orange-700";
|
||||
case "Critical":
|
||||
return "red-500";
|
||||
return "text-red-400 dark:text-red-700";
|
||||
case "Unknown":
|
||||
return "purple-500";
|
||||
return "text-purple-400 dark:text-purple-700";
|
||||
case "None":
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const fill_color_class_by_intention = (intention: ColorIntention) => {
|
||||
if (intention === "None") return "";
|
||||
return `fill-${color_class_by_intention(intention)}`;
|
||||
};
|
||||
|
||||
export const stroke_color_class_by_intention = (intention: ColorIntention) => {
|
||||
if (intention === "None") return "";
|
||||
return `stroke-${color_class_by_intention(intention)}`;
|
||||
switch (intention) {
|
||||
case "Good":
|
||||
return "stroke-green-600 dark:stroke-green-500";
|
||||
case "Neutral":
|
||||
return "stroke-blue-600 dark:stroke-blue-500";
|
||||
case "Warning":
|
||||
return "stroke-orange-600 dark:stroke-orange-500";
|
||||
case "Critical":
|
||||
return "stroke-red-600 dark:stroke-red-500";
|
||||
case "Unknown":
|
||||
return "stroke-purple-600 dark:stroke-purple-500";
|
||||
case "None":
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const bg_color_class_by_intention = (intention: ColorIntention) => {
|
||||
@@ -72,15 +79,15 @@ export const bg_color_class_by_intention = (intention: ColorIntention) => {
|
||||
export const text_color_class_by_intention = (intention: ColorIntention) => {
|
||||
switch (intention) {
|
||||
case "Good":
|
||||
return "text-green-500";
|
||||
return "text-green-700 dark:text-green-400";
|
||||
case "Neutral":
|
||||
return "text-blue-500";
|
||||
return "text-blue-700 dark:text-blue-400";
|
||||
case "Warning":
|
||||
return "text-orange-500";
|
||||
return "text-orange-700 dark:text-orange-400";
|
||||
case "Critical":
|
||||
return "text-red-500";
|
||||
return "text-red-700 dark:text-red-400";
|
||||
case "Unknown":
|
||||
return "text-purple-500";
|
||||
return "text-purple-700 dark:text-purple-400";
|
||||
case "None":
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Types } from "@monitor/client";
|
||||
export const fmt_date = (d: Date) => {
|
||||
const hours = d.getHours();
|
||||
const minutes = d.getMinutes();
|
||||
return `${fmt_month(d.getMonth())} ${d.getDate()} @ ${
|
||||
return `${fmt_month(d.getMonth())} ${d.getDate()} ${
|
||||
hours > 9 ? hours : "0" + hours
|
||||
}:${minutes > 9 ? minutes : "0" + minutes}`;
|
||||
};
|
||||
|
||||
@@ -97,8 +97,8 @@ export const useManageUser = <
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => client().user({ type, params } as R),
|
||||
onError: (e, v, c) => {
|
||||
console.log("useManageUser error:", e);
|
||||
onError: (e: { response: { data: any } }, v, c) => {
|
||||
console.log("Auth error:", e.response.data);
|
||||
toast({
|
||||
title: `Request ${type} Failed`,
|
||||
description: "See console for details",
|
||||
@@ -126,8 +126,8 @@ export const useWrite = <
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => client().write({ type, params } as R),
|
||||
onError: (e, v, c) => {
|
||||
console.log("useWrite error:", e);
|
||||
onError: (e: { response: { data: any } }, v, c) => {
|
||||
console.log("Write error:", e.response.data);
|
||||
toast({
|
||||
title: `Write request ${type} failed`,
|
||||
description: "See console for details",
|
||||
@@ -155,8 +155,8 @@ export const useExecute = <
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => client().execute({ type, params } as R),
|
||||
onError: (e, v, c) => {
|
||||
console.log("useExecute error:", e);
|
||||
onError: (e: { response: { data: any } }, v, c) => {
|
||||
console.log("Execute error:", e.response.data);
|
||||
toast({
|
||||
title: `Execute request ${type} failed`,
|
||||
description: "See console for details",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExportButton } from "@components/export";
|
||||
import { ConfirmButton, TextUpdateMenu } from "@components/util";
|
||||
import { ConfirmButton, CopyButton, TextUpdateMenu } from "@components/util";
|
||||
import {
|
||||
useInvalidate,
|
||||
useRead,
|
||||
@@ -108,26 +108,29 @@ export const Variables = () => {
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Card
|
||||
className="px-3 py-2 hover:bg-accent/50 transition-colors cursor-pointer w-full"
|
||||
onClick={() => {
|
||||
setUpdateMenuData({
|
||||
title: `${row.original.name} - Value`,
|
||||
value: row.original.value ?? "",
|
||||
placeholder: "Set value",
|
||||
onUpdate: (value) => {
|
||||
if (row.original.value === value) {
|
||||
return;
|
||||
}
|
||||
updateValue({ name: row.original.name, value });
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="text-sm text-nowrap overflow-hidden overflow-ellipsis w-full text-muted-foreground">
|
||||
{row.original.value || "Set value"}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex items-center gap-2">
|
||||
<Card
|
||||
className="px-3 py-2 hover:bg-accent/50 transition-colors cursor-pointer w-full"
|
||||
onClick={() => {
|
||||
setUpdateMenuData({
|
||||
title: `${row.original.name} - Value`,
|
||||
value: row.original.value ?? "",
|
||||
placeholder: "Set value",
|
||||
onUpdate: (value) => {
|
||||
if (row.original.value === value) {
|
||||
return;
|
||||
}
|
||||
updateValue({ name: row.original.name, value });
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="text-sm text-nowrap overflow-hidden overflow-ellipsis w-full text-muted-foreground">
|
||||
{row.original.value || "Set value"}
|
||||
</div>
|
||||
</Card>
|
||||
<CopyButton content={row.original.value} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,10 +2,97 @@
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
"./pages/**/*.{ts,tsx}",
|
||||
"./components/**/*.{ts,tsx}",
|
||||
"./app/**/*.{ts,tsx}",
|
||||
"./src/**/*.{ts,tsx}",
|
||||
],
|
||||
safelist: [
|
||||
// red
|
||||
"text-red-400",
|
||||
"text-red-500",
|
||||
"text-red-600",
|
||||
"text-red-700",
|
||||
"bg-red-400",
|
||||
"bg-red-500",
|
||||
"bg-red-600",
|
||||
"bg-red-700",
|
||||
"fill-red-400",
|
||||
"fill-red-500",
|
||||
"fill-red-600",
|
||||
"fill-red-700",
|
||||
"stroke-red-400",
|
||||
"stroke-red-500",
|
||||
"stroke-red-600",
|
||||
"stroke-red-700",
|
||||
// green
|
||||
"text-green-400",
|
||||
"text-green-500",
|
||||
"text-green-600",
|
||||
"text-green-700",
|
||||
"bg-green-400",
|
||||
"bg-green-500",
|
||||
"bg-green-600",
|
||||
"bg-green-700",
|
||||
"fill-green-400",
|
||||
"fill-green-500",
|
||||
"fill-green-600",
|
||||
"fill-green-700",
|
||||
"stroke-green-400",
|
||||
"stroke-green-500",
|
||||
"stroke-green-600",
|
||||
"stroke-green-700",
|
||||
// blue
|
||||
"text-blue-400",
|
||||
"text-blue-500",
|
||||
"text-blue-600",
|
||||
"text-blue-700",
|
||||
"bg-blue-400",
|
||||
"bg-blue-500",
|
||||
"bg-blue-600",
|
||||
"bg-blue-700",
|
||||
"fill-blue-400",
|
||||
"fill-blue-500",
|
||||
"fill-blue-600",
|
||||
"fill-blue-700",
|
||||
"stroke-blue-400",
|
||||
"stroke-blue-500",
|
||||
"stroke-blue-600",
|
||||
"stroke-blue-700",
|
||||
// orange
|
||||
"text-orange-400",
|
||||
"text-orange-500",
|
||||
"text-orange-600",
|
||||
"text-orange-700",
|
||||
"bg-orange-400",
|
||||
"bg-orange-500",
|
||||
"bg-orange-600",
|
||||
"bg-orange-700",
|
||||
"fill-orange-400",
|
||||
"fill-orange-500",
|
||||
"fill-orange-600",
|
||||
"fill-orange-700",
|
||||
"stroke-orange-400",
|
||||
"stroke-orange-500",
|
||||
"stroke-orange-600",
|
||||
"stroke-orange-700",
|
||||
// purple
|
||||
"text-purple-400",
|
||||
"text-purple-500",
|
||||
"text-purple-600",
|
||||
"text-purple-700",
|
||||
"bg-purple-400",
|
||||
"bg-purple-500",
|
||||
"bg-purple-600",
|
||||
"bg-purple-700",
|
||||
"fill-purple-400",
|
||||
"fill-purple-500",
|
||||
"fill-purple-600",
|
||||
"fill-purple-700",
|
||||
"stroke-purple-400",
|
||||
"stroke-purple-500",
|
||||
"stroke-purple-600",
|
||||
"stroke-purple-700",
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
@@ -74,4 +161,4 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "formatting"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,32 @@
|
||||
pub fn muted(content: impl std::fmt::Display) -> String {
|
||||
format!("<span class=\"text-muted-foreground\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn bold(content: impl std::fmt::Display) -> String {
|
||||
format!("<span class=\"font-bold\">{content}</span>")
|
||||
}
|
||||
|
||||
pub fn colored(
|
||||
content: impl std::fmt::Display,
|
||||
color: Color,
|
||||
) -> String {
|
||||
format!("<span class=\"{color}\">{content}</span>")
|
||||
}
|
||||
|
||||
pub enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Color::Red => f.write_str("text-red-700 dark:text-red-400"),
|
||||
Color::Green => {
|
||||
f.write_str("text-green-700 dark:text-green-400")
|
||||
}
|
||||
Color::Blue => f.write_str("text-blue-700 dark:text-blue-400"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ homepage.workspace = true
|
||||
|
||||
[dependencies]
|
||||
monitor_client.workspace = true
|
||||
formatting.workspace = true
|
||||
command.workspace = true
|
||||
#
|
||||
run_command.workspace = true
|
||||
|
||||
+18
-3
@@ -1,7 +1,11 @@
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use command::run_monitor_command;
|
||||
use formatting::{bold, muted};
|
||||
use monitor_client::entities::{
|
||||
all_logs_success, monitor_timestamp, to_monitor_name, update::Log,
|
||||
CloneArgs, LatestCommit, SystemCommand,
|
||||
@@ -77,6 +81,7 @@ where
|
||||
repo,
|
||||
branch,
|
||||
commit,
|
||||
destination,
|
||||
on_clone,
|
||||
on_pull,
|
||||
..
|
||||
@@ -85,7 +90,11 @@ where
|
||||
let repo = repo.as_ref().context("build has no repo attached")?;
|
||||
let name = to_monitor_name(&name);
|
||||
|
||||
let repo_dir = repo_dir.join(name);
|
||||
let repo_dir = match destination {
|
||||
Some(destination) => PathBuf::from_str(&destination)
|
||||
.context("destination is not valid path")?,
|
||||
None => repo_dir.join(name),
|
||||
};
|
||||
|
||||
let mut logs =
|
||||
clone_inner(repo, &repo_dir, &branch, &commit, github_token)
|
||||
@@ -244,7 +253,13 @@ pub async fn get_commit_hash_log(
|
||||
let log = Log {
|
||||
stage: "latest commit".into(),
|
||||
command,
|
||||
stdout: format!("hash: {short}\nmessage: {msg}"),
|
||||
stdout: format!(
|
||||
"{} {}\n{} {}",
|
||||
muted("hash:"),
|
||||
bold(short),
|
||||
muted("message:"),
|
||||
bold(msg),
|
||||
),
|
||||
stderr: String::new(),
|
||||
success: true,
|
||||
start_ts,
|
||||
|
||||
@@ -6,84 +6,7 @@ Docs for periphery setup script can be found in [scripts/readme.md](https://gith
|
||||
|
||||
## Changelog
|
||||
|
||||
### <ins>v1 (Spring 2024)</ins>
|
||||
|
||||
- **New resource types**:
|
||||
- **Repo**: Clone / pull configured repositories on desired Server. Run shell commands in the repo on every clone / pull to acheive automation. Listen for pushes to a particular branch to automatically pull the repo and run the command.
|
||||
- **Procedure**: Combine multiple *executions* (Deploy, RunBuild) and run them in sequence or in parallel. *RunProcedure* is an execution type, meaning procedures can run *other procedures*.
|
||||
- **Builder**: Ephemeral builder configuration has moved to being an API / UI managed entity for greater observability and ease of management.
|
||||
- **Alerter**: Define multiple alerting endpoints and manage them via the API / UI.
|
||||
- Slack support continues with the *Slack* Alerter variant.
|
||||
- Send JSON serialized alert data to any HTTP endpoint with the *Custom* Alerter variant.
|
||||
- **Template**: Define a template for your cloud provider's VM configuration
|
||||
- Launch VMs based on the template and automatically add them as Monitor servers.
|
||||
- Supports AWS EC2 and Hetzner Cloud.
|
||||
|
||||
- **Resource Tagging**
|
||||
- Attach multiple *tags* to resources, which can be used to group related resources together. These can be used to filter resources in the UI.
|
||||
- For example, resources can be given tags for *environment*, like `Prod`, `Uat`, or `Dev`. This can be combined with tags for the larger system the resource is a part of, such as `Authentication`, `Logging`, or `Messaging`.
|
||||
- Proper tagging will make it easy to find resources and visualize system components, even as the number of resources grows large.
|
||||
|
||||
- **Variables**
|
||||
- Manage global, non-secret key-value pairs using the API / UI.
|
||||
- These values can be interpolated into deployment `environments` and build `build_args`
|
||||
|
||||
- **Core Accounts and Secrets**
|
||||
- Docker / Github accounts and Secrets can now be configured in the Core configuration file.
|
||||
- They can still be added to the Periphery configuration as before. Accounts / Secrets defined in the Core configuration will be preferentially used over like ones defined in Periphery configuration.
|
||||
|
||||
- **User Groups**
|
||||
- Admins can now create User Groups and assign permissions to them as if they were a user.
|
||||
- Multiple users can then be added to the group, and a user can be added to multiple groups.
|
||||
- Users in the group inherit the group's permissions.
|
||||
|
||||
- **Monitor CLI + Sync**
|
||||
- Introduces the [monitor cli](https://crates.io/crates/monitor_cli), which can sync resources declared across multiple toml files.
|
||||
- Implements granular diffing of local and remote resources, producing detailed logs of the changes which will be made before user confirm.
|
||||
- Resource files can be checked into git and managed via PR, enabling easy scaling even with large numbers of resources.
|
||||
- All UI resources include button to export the resource to TOML for easy addition to your resource file. There is also a button to export all resources
|
||||
to make the move to file managed resources.
|
||||
|
||||
- **Builds**
|
||||
- Build log displays the **latest commit hash and message**.
|
||||
- In-progress builds are able to be **cancelled before completion**.
|
||||
- Specify a specific commit hash to build from.
|
||||
|
||||
- **Deployments**
|
||||
- Filter log lines using multiple search terms.
|
||||
|
||||
- **Alerting**
|
||||
- The alerting system has been redesigned with a stateful model.
|
||||
- Alerts can be in an Open or a Resolved state, and alerts are only sent on state changes.
|
||||
- For example, say a server has just crossed 80% memory usage, the configured memory threshold. An alert will be created in the Open state and the alert data will be sent out. Later, it has dropped down to 70%. The alert will be changed to the Resolved state and the alert data will again be sent.
|
||||
- In addition to server usage alerts, Monitor now supports deployment state change alerts. These are sent when a deployment's state changes without being caused by a Monitor action. For example, if a deployment goes from the Running state to the Exited state unexpectedly, say from a crash, an alert will be sent.
|
||||
- Current and past alerts can be retrieved using the API and viewed on the UI.
|
||||
|
||||
- **New UI**:
|
||||
- The Monitor UI has been revamped to support the new features and improve the user experience.
|
||||
|
||||
### <ins>v0 (Winter 2022)</ins>
|
||||
|
||||
- Move Core and Periphery implementation to Rust.
|
||||
- Add AWS Ec2 ephemeral build instance support.
|
||||
- Configuration added to core configuration file.
|
||||
- Automatic build versioning system, supporting image rollback.
|
||||
- Realtime and historical system stats - CPU, memory, disk.
|
||||
- Simple stats alerting based on out-of-bounds values for system stats.
|
||||
- Support sending alerts to Slack.
|
||||
|
||||
### <ins>Pre-versioned releases</ins>
|
||||
|
||||
- Defined main resource types:
|
||||
- Server
|
||||
- Deployment
|
||||
- Build
|
||||
- Basics of Monitor:
|
||||
- Build docker images from Github repos.
|
||||
- Manage image deployment on connected servers, see container status, get container logs.
|
||||
- Add account credentials in Periphery configuration.
|
||||
- Core and Periphery implemented in Typescript.
|
||||
|
||||
View the [changelog](https://github.com/mbecker20/monitor/blob/main/changelog.md)
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
Reference in New Issue
Block a user