diff --git a/Cargo.lock b/Cargo.lock index 9ec1795fd..7f2f0b3ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "alerter" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "axum", @@ -943,7 +943,7 @@ dependencies = [ [[package]] name = "command" -version = "1.15.8" +version = "1.15.9" dependencies = [ "komodo_client", "run_command", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.15.8" +version = "1.15.9" dependencies = [ "thiserror", ] @@ -1439,7 +1439,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.15.8" +version = "1.15.9" dependencies = [ "serror", ] @@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "git" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "command", @@ -2192,7 +2192,7 @@ dependencies = [ [[package]] name = "komodo_cli" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "clap", @@ -2208,7 +2208,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "async_timing_util", @@ -2239,7 +2239,7 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "async_timing_util", @@ -2296,7 +2296,7 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "async_timing_util", @@ -2383,7 +2383,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "komodo_client", @@ -2445,19 +2445,6 @@ dependencies = [ "toml", ] -[[package]] -name = "migrator" -version = "1.15.8" -dependencies = [ - "anyhow", - "dotenvy", - "envy", - "logger", - "serde", - "tokio", - "tracing", -] - [[package]] name = "mime" version = "0.3.17" @@ -3102,7 +3089,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "komodo_client", @@ -4880,7 +4867,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "update_logger" -version = "1.15.8" +version = "1.15.9" dependencies = [ "anyhow", "komodo_client", diff --git a/Cargo.toml b/Cargo.toml index bc1b84373..7d0068424 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,15 @@ [workspace] resolver = "2" -members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"] +members = [ + "bin/*", + "lib/*", + "example/*", + "client/core/rs", + "client/periphery/rs", +] [workspace.package] -version = "1.15.8" +version = "1.15.9" edition = "2021" authors = ["mbecker20 "] license = "GPL-3.0-or-later" @@ -108,4 +114,4 @@ octorust = "0.7.0" dashmap = "6.1.0" colored = "2.1.0" regex = "1.11.0" -bson = "2.13.0" \ No newline at end of file +bson = "2.13.0" diff --git a/bin/core/alpine.Dockerfile b/bin/core/alpine.Dockerfile index aa8dc7ad8..5826bdd32 100644 --- a/bin/core/alpine.Dockerfile +++ b/bin/core/alpine.Dockerfile @@ -16,7 +16,7 @@ WORKDIR /builder COPY ./frontend ./frontend COPY ./client/core/ts ./client RUN cd client && yarn && yarn build && yarn link -RUN cd frontend && yarn link @komodo/client && yarn && yarn build +RUN cd frontend && yarn link komodo_client && yarn && yarn build # Final Image FROM alpine:3.20 diff --git a/bin/core/debian.Dockerfile b/bin/core/debian.Dockerfile index 6de909eda..c0c66a625 100644 --- a/bin/core/debian.Dockerfile +++ b/bin/core/debian.Dockerfile @@ -10,7 +10,7 @@ WORKDIR /builder COPY ./frontend ./frontend COPY ./client/core/ts ./client RUN cd client && yarn && yarn build && yarn link -RUN cd frontend && yarn link @komodo/client && yarn && yarn build +RUN cd frontend && yarn link komodo_client && yarn && yarn build # Final Image FROM debian:bullseye-slim diff --git a/bin/core/src/listener/github/build.rs b/bin/core/src/listener/github/build.rs index 82f998cb3..e91afe092 100644 --- a/bin/core/src/listener/github/build.rs +++ b/bin/core/src/listener/github/build.rs @@ -6,7 +6,9 @@ use komodo_client::{ api::execute::RunBuild, entities::{build::Build, user::git_webhook_user}, }; +use reqwest::StatusCode; use resolver_api::Resolve; +use serror::AddStatusCode; use crate::{ api::execute::ExecuteRequest, @@ -20,22 +22,30 @@ fn build_locks() -> &'static ListenerLockCache { BUILD_LOCKS.get_or_init(Default::default) } -pub async fn handle_build_webhook( - build_id: String, +pub async fn auth_build_webhook( + build_id: &str, headers: HeaderMap, + body: &str, +) -> serror::Result { + let build = resource::get::(build_id) + .await + .status_code(StatusCode::NOT_FOUND)?; + verify_gh_signature(headers, body, &build.config.webhook_secret) + .await + .status_code(StatusCode::UNAUTHORIZED)?; + Ok(build) +} + +pub async fn handle_build_webhook( + build: Build, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = build_locks().get_or_insert_default(&build_id).await; + let lock = build_locks().get_or_insert_default(&build.id).await; let _lock = lock.lock().await; - let build = resource::get::(&build_id).await?; - - verify_gh_signature(headers, &body, &build.config.webhook_secret) - .await?; - if !build.config.webhook_enabled { return Err(anyhow!("build does not have webhook enabled")); } @@ -46,7 +56,7 @@ pub async fn handle_build_webhook( } let user = git_webhook_user().to_owned(); - let req = ExecuteRequest::RunBuild(RunBuild { build: build_id }); + let req = ExecuteRequest::RunBuild(RunBuild { build: build.id }); let update = init_execution_update(&req, &user).await?; let ExecuteRequest::RunBuild(req) = req else { unreachable!() diff --git a/bin/core/src/listener/github/mod.rs b/bin/core/src/listener/github/mod.rs index 428ded0c8..0834d6fd4 100644 --- a/bin/core/src/listener/github/mod.rs +++ b/bin/core/src/listener/github/mod.rs @@ -39,10 +39,11 @@ pub fn router() -> Router { "/build/:id", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let build = build::auth_build_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("build_webhook", id); async { - let res = build::handle_build_webhook(id.clone(), headers, body).await; + let res = build::handle_build_webhook(build, body).await; if let Err(e) = res { warn!("failed to run build webook for build {id} | {e:#}"); } @@ -50,6 +51,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ), ) @@ -57,10 +59,11 @@ pub fn router() -> Router { "/repo/:id/clone", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let repo = repo::auth_repo_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("repo_clone_webhook", id); async { - let res = repo::handle_repo_clone_webhook(id.clone(), headers, body).await; + let res = repo::handle_repo_clone_webhook(repo, body).await; if let Err(e) = res { warn!("failed to run repo clone webook for repo {id} | {e:#}"); } @@ -68,6 +71,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -75,10 +79,11 @@ pub fn router() -> Router { "/repo/:id/pull", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let repo = repo::auth_repo_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("repo_pull_webhook", id); async { - let res = repo::handle_repo_pull_webhook(id.clone(), headers, body).await; + let res = repo::handle_repo_pull_webhook(repo, body).await; if let Err(e) = res { warn!("failed to run repo pull webook for repo {id} | {e:#}"); } @@ -86,6 +91,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -93,10 +99,11 @@ pub fn router() -> Router { "/repo/:id/build", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let repo = repo::auth_repo_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("repo_build_webhook", id); async { - let res = repo::handle_repo_build_webhook(id.clone(), headers, body).await; + let res = repo::handle_repo_build_webhook(repo, body).await; if let Err(e) = res { warn!("failed to run repo build webook for repo {id} | {e:#}"); } @@ -104,6 +111,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -111,10 +119,11 @@ pub fn router() -> Router { "/stack/:id/refresh", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let stack = stack::auth_stack_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("stack_clone_webhook", id); async { - let res = stack::handle_stack_refresh_webhook(id.clone(), headers, body).await; + let res = stack::handle_stack_refresh_webhook(stack, body).await; if let Err(e) = res { warn!("failed to run stack clone webook for stack {id} | {e:#}"); } @@ -122,6 +131,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -129,10 +139,11 @@ pub fn router() -> Router { "/stack/:id/deploy", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let stack = stack::auth_stack_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("stack_pull_webhook", id); async { - let res = stack::handle_stack_deploy_webhook(id.clone(), headers, body).await; + let res = stack::handle_stack_deploy_webhook(stack, body).await; if let Err(e) = res { warn!("failed to run stack pull webook for stack {id} | {e:#}"); } @@ -140,6 +151,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -147,13 +159,13 @@ pub fn router() -> Router { "/procedure/:id/:branch", post( |Path(IdBranch { id, branch }), headers: HeaderMap, body: String| async move { + let procedure = procedure::auth_procedure_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("procedure_webhook", id, branch); async { let res = procedure::handle_procedure_webhook( - id.clone(), + procedure, branch.unwrap_or_else(|| String::from("main")), - headers, body ).await; if let Err(e) = res { @@ -163,6 +175,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -170,12 +183,12 @@ pub fn router() -> Router { "/sync/:id/refresh", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let sync = sync::auth_sync_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("sync_refresh_webhook", id); async { let res = sync::handle_sync_refresh_webhook( - id.clone(), - headers, + sync, body ).await; if let Err(e) = res { @@ -185,6 +198,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) @@ -192,12 +206,12 @@ pub fn router() -> Router { "/sync/:id/sync", post( |Path(Id { id }), headers: HeaderMap, body: String| async move { + let sync = sync::auth_sync_webhook(&id, headers, &body).await?; tokio::spawn(async move { let span = info_span!("sync_execute_webhook", id); async { let res = sync::handle_sync_execute_webhook( - id.clone(), - headers, + sync, body ).await; if let Err(e) = res { @@ -207,6 +221,7 @@ pub fn router() -> Router { .instrument(span) .await }); + serror::Result::Ok(()) }, ) ) diff --git a/bin/core/src/listener/github/procedure.rs b/bin/core/src/listener/github/procedure.rs index 472ed1ad4..987500612 100644 --- a/bin/core/src/listener/github/procedure.rs +++ b/bin/core/src/listener/github/procedure.rs @@ -6,7 +6,9 @@ use komodo_client::{ api::execute::RunProcedure, entities::{procedure::Procedure, user::git_webhook_user}, }; +use reqwest::StatusCode; use resolver_api::Resolve; +use serror::AddStatusCode; use crate::{ api::execute::ExecuteRequest, @@ -20,28 +22,36 @@ fn procedure_locks() -> &'static ListenerLockCache { BUILD_LOCKS.get_or_init(Default::default) } +pub async fn auth_procedure_webhook( + procedure_id: &str, + headers: HeaderMap, + body: &str, +) -> serror::Result { + let procedure = resource::get::(procedure_id) + .await + .status_code(StatusCode::NOT_FOUND)?; + verify_gh_signature( + headers, + body, + &procedure.config.webhook_secret, + ) + .await + .status_code(StatusCode::UNAUTHORIZED)?; + Ok(procedure) +} + pub async fn handle_procedure_webhook( - procedure_id: String, + procedure: Procedure, target_branch: String, - headers: HeaderMap, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. let lock = - procedure_locks().get_or_insert_default(&procedure_id).await; + procedure_locks().get_or_insert_default(&procedure.id).await; let _lock = lock.lock().await; - let procedure = resource::get::(&procedure_id).await?; - - verify_gh_signature( - headers, - &body, - &procedure.config.webhook_secret, - ) - .await?; - if !procedure.config.webhook_enabled { return Err(anyhow!("procedure does not have webhook enabled")); } @@ -53,7 +63,7 @@ pub async fn handle_procedure_webhook( let user = git_webhook_user().to_owned(); let req = ExecuteRequest::RunProcedure(RunProcedure { - procedure: procedure_id, + procedure: procedure.id, }); let update = init_execution_update(&req, &user).await?; let ExecuteRequest::RunProcedure(req) = req else { diff --git a/bin/core/src/listener/github/repo.rs b/bin/core/src/listener/github/repo.rs index 3396739b6..d24ca59a4 100644 --- a/bin/core/src/listener/github/repo.rs +++ b/bin/core/src/listener/github/repo.rs @@ -6,7 +6,9 @@ use komodo_client::{ api::execute::{BuildRepo, CloneRepo, PullRepo}, entities::{repo::Repo, user::git_webhook_user}, }; +use reqwest::StatusCode; use resolver_api::Resolve; +use serror::AddStatusCode; use crate::{ helpers::update::init_execution_update, resource, state::State, @@ -19,22 +21,30 @@ fn repo_locks() -> &'static ListenerLockCache { REPO_LOCKS.get_or_init(Default::default) } -pub async fn handle_repo_clone_webhook( - repo_id: String, +pub async fn auth_repo_webhook( + repo_id: &str, headers: HeaderMap, + body: &str, +) -> serror::Result { + let repo = resource::get::(repo_id) + .await + .status_code(StatusCode::NOT_FOUND)?; + verify_gh_signature(headers, body, &repo.config.webhook_secret) + .await + .status_code(StatusCode::UNAUTHORIZED)?; + Ok(repo) +} + +pub async fn handle_repo_clone_webhook( + repo: Repo, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = repo_locks().get_or_insert_default(&repo_id).await; + let lock = repo_locks().get_or_insert_default(&repo.id).await; let _lock = lock.lock().await; - let repo = resource::get::(&repo_id).await?; - - verify_gh_signature(headers, &body, &repo.config.webhook_secret) - .await?; - if !repo.config.webhook_enabled { return Err(anyhow!("repo does not have webhook enabled")); } @@ -47,7 +57,7 @@ pub async fn handle_repo_clone_webhook( let user = git_webhook_user().to_owned(); let req = crate::api::execute::ExecuteRequest::CloneRepo(CloneRepo { - repo: repo_id, + repo: repo.id, }); let update = init_execution_update(&req, &user).await?; let crate::api::execute::ExecuteRequest::CloneRepo(req) = req @@ -59,21 +69,15 @@ pub async fn handle_repo_clone_webhook( } pub async fn handle_repo_pull_webhook( - repo_id: String, - headers: HeaderMap, + repo: Repo, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = repo_locks().get_or_insert_default(&repo_id).await; + let lock = repo_locks().get_or_insert_default(&repo.id).await; let _lock = lock.lock().await; - let repo = resource::get::(&repo_id).await?; - - verify_gh_signature(headers, &body, &repo.config.webhook_secret) - .await?; - if !repo.config.webhook_enabled { return Err(anyhow!("repo does not have webhook enabled")); } @@ -85,7 +89,7 @@ pub async fn handle_repo_pull_webhook( let user = git_webhook_user().to_owned(); let req = crate::api::execute::ExecuteRequest::PullRepo(PullRepo { - repo: repo_id, + repo: repo.id, }); let update = init_execution_update(&req, &user).await?; let crate::api::execute::ExecuteRequest::PullRepo(req) = req else { @@ -96,21 +100,15 @@ pub async fn handle_repo_pull_webhook( } pub async fn handle_repo_build_webhook( - repo_id: String, - headers: HeaderMap, + repo: Repo, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = repo_locks().get_or_insert_default(&repo_id).await; + let lock = repo_locks().get_or_insert_default(&repo.id).await; let _lock = lock.lock().await; - let repo = resource::get::(&repo_id).await?; - - verify_gh_signature(headers, &body, &repo.config.webhook_secret) - .await?; - if !repo.config.webhook_enabled { return Err(anyhow!("repo does not have webhook enabled")); } @@ -123,7 +121,7 @@ pub async fn handle_repo_build_webhook( let user = git_webhook_user().to_owned(); let req = crate::api::execute::ExecuteRequest::BuildRepo(BuildRepo { - repo: repo_id, + repo: repo.id, }); let update = init_execution_update(&req, &user).await?; let crate::api::execute::ExecuteRequest::BuildRepo(req) = req diff --git a/bin/core/src/listener/github/stack.rs b/bin/core/src/listener/github/stack.rs index f97b9e30c..e8ee3075a 100644 --- a/bin/core/src/listener/github/stack.rs +++ b/bin/core/src/listener/github/stack.rs @@ -9,7 +9,9 @@ use komodo_client::{ }, entities::{stack::Stack, user::git_webhook_user}, }; +use reqwest::StatusCode; use resolver_api::Resolve; +use serror::AddStatusCode; use crate::{ api::execute::ExecuteRequest, @@ -23,22 +25,30 @@ fn stack_locks() -> &'static ListenerLockCache { STACK_LOCKS.get_or_init(Default::default) } -pub async fn handle_stack_refresh_webhook( - stack_id: String, +pub async fn auth_stack_webhook( + stack_id: &str, headers: HeaderMap, + body: &str, +) -> serror::Result { + let stack = resource::get::(stack_id) + .await + .status_code(StatusCode::NOT_FOUND)?; + verify_gh_signature(headers, body, &stack.config.webhook_secret) + .await + .status_code(StatusCode::UNAUTHORIZED)?; + Ok(stack) +} + +pub async fn handle_stack_refresh_webhook( + stack: Stack, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through, from "action state busy". - let lock = stack_locks().get_or_insert_default(&stack_id).await; + let lock = stack_locks().get_or_insert_default(&stack.id).await; let _lock = lock.lock().await; - let stack = resource::get::(&stack_id).await?; - - verify_gh_signature(headers, &body, &stack.config.webhook_secret) - .await?; - if !stack.config.webhook_enabled { return Err(anyhow!("stack does not have webhook enabled")); } @@ -56,21 +66,15 @@ pub async fn handle_stack_refresh_webhook( } pub async fn handle_stack_deploy_webhook( - stack_id: String, - headers: HeaderMap, + stack: Stack, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = stack_locks().get_or_insert_default(&stack_id).await; + let lock = stack_locks().get_or_insert_default(&stack.id).await; let _lock = lock.lock().await; - let stack = resource::get::(&stack_id).await?; - - verify_gh_signature(headers, &body, &stack.config.webhook_secret) - .await?; - if !stack.config.webhook_enabled { return Err(anyhow!("stack does not have webhook enabled")); } @@ -83,7 +87,7 @@ pub async fn handle_stack_deploy_webhook( let user = git_webhook_user().to_owned(); if stack.config.webhook_force_deploy { let req = ExecuteRequest::DeployStack(DeployStack { - stack: stack_id, + stack: stack.id, stop_time: None, }); let update = init_execution_update(&req, &user).await?; @@ -94,7 +98,7 @@ pub async fn handle_stack_deploy_webhook( } else { let req = ExecuteRequest::DeployStackIfChanged(DeployStackIfChanged { - stack: stack_id, + stack: stack.id, stop_time: None, }); let update = init_execution_update(&req, &user).await?; diff --git a/bin/core/src/listener/github/sync.rs b/bin/core/src/listener/github/sync.rs index 5d06df0dd..08df847d2 100644 --- a/bin/core/src/listener/github/sync.rs +++ b/bin/core/src/listener/github/sync.rs @@ -6,7 +6,9 @@ use komodo_client::{ api::{execute::RunSync, write::RefreshResourceSyncPending}, entities::{sync::ResourceSync, user::git_webhook_user}, }; +use reqwest::StatusCode; use resolver_api::Resolve; +use serror::AddStatusCode; use crate::{ api::execute::ExecuteRequest, @@ -20,22 +22,30 @@ fn sync_locks() -> &'static ListenerLockCache { SYNC_LOCKS.get_or_init(Default::default) } -pub async fn handle_sync_refresh_webhook( - sync_id: String, +pub async fn auth_sync_webhook( + sync_id: &str, headers: HeaderMap, + body: &str, +) -> serror::Result { + let sync = resource::get::(sync_id) + .await + .status_code(StatusCode::NOT_FOUND)?; + verify_gh_signature(headers, body, &sync.config.webhook_secret) + .await + .status_code(StatusCode::UNAUTHORIZED)?; + Ok(sync) +} + +pub async fn handle_sync_refresh_webhook( + sync: ResourceSync, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = sync_locks().get_or_insert_default(&sync_id).await; + let lock = sync_locks().get_or_insert_default(&sync.id).await; let _lock = lock.lock().await; - let sync = resource::get::(&sync_id).await?; - - verify_gh_signature(headers, &body, &sync.config.webhook_secret) - .await?; - if !sync.config.webhook_enabled { return Err(anyhow!("sync does not have webhook enabled")); } @@ -47,27 +57,21 @@ pub async fn handle_sync_refresh_webhook( let user = git_webhook_user().to_owned(); State - .resolve(RefreshResourceSyncPending { sync: sync_id }, user) + .resolve(RefreshResourceSyncPending { sync: sync.id }, user) .await?; Ok(()) } pub async fn handle_sync_execute_webhook( - sync_id: String, - headers: HeaderMap, + sync: ResourceSync, body: String, ) -> anyhow::Result<()> { // Acquire and hold lock to make a task queue for // subsequent listener calls on same resource. // It would fail if we let it go through from action state busy. - let lock = sync_locks().get_or_insert_default(&sync_id).await; + let lock = sync_locks().get_or_insert_default(&sync.id).await; let _lock = lock.lock().await; - let sync = resource::get::(&sync_id).await?; - - verify_gh_signature(headers, &body, &sync.config.webhook_secret) - .await?; - if !sync.config.webhook_enabled { return Err(anyhow!("sync does not have webhook enabled")); } @@ -79,7 +83,7 @@ pub async fn handle_sync_execute_webhook( let user = git_webhook_user().to_owned(); let req = ExecuteRequest::RunSync(RunSync { - sync: sync_id, + sync: sync.id, resource_type: None, resources: None, }); diff --git a/bin/core/src/monitor/alert/mod.rs b/bin/core/src/monitor/alert/mod.rs index 639c219dc..d370becd2 100644 --- a/bin/core/src/monitor/alert/mod.rs +++ b/bin/core/src/monitor/alert/mod.rs @@ -2,9 +2,7 @@ use std::collections::HashMap; use anyhow::Context; use komodo_client::entities::{ - resource::ResourceQuery, - server::{Server, ServerListItem}, - user::User, + resource::ResourceQuery, server::Server, user::User, }; use crate::resource; @@ -32,11 +30,10 @@ pub async fn check_alerts(ts: i64) { } #[instrument(level = "debug")] -async fn get_all_servers_map() -> anyhow::Result<( - HashMap, - HashMap, -)> { - let servers = resource::list_for_user::( +async fn get_all_servers_map( +) -> anyhow::Result<(HashMap, HashMap)> +{ + let servers = resource::list_full_for_user::( ResourceQuery::default(), &User { admin: true, diff --git a/bin/core/src/monitor/alert/server.rs b/bin/core/src/monitor/alert/server.rs index aa0210a17..c7298fae2 100644 --- a/bin/core/src/monitor/alert/server.rs +++ b/bin/core/src/monitor/alert/server.rs @@ -5,7 +5,7 @@ use derive_variants::ExtractVariant; use komodo_client::entities::{ alert::{Alert, AlertData, AlertDataVariant, SeverityLevel}, komodo_timestamp, optional_string, - server::{ServerListItem, ServerState}, + server::{Server, ServerState}, ResourceTarget, }; use mongo_indexed::Indexed; @@ -28,7 +28,7 @@ type OpenDiskAlertMap = OpenAlertMap; #[instrument(level = "debug")] pub async fn alert_servers( ts: i64, - mut servers: HashMap, + mut servers: HashMap, ) { let server_statuses = server_status_cache().get_list().await; @@ -70,12 +70,12 @@ pub async fn alert_servers( data: AlertData::ServerUnreachable { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), err: server_status.err.clone(), }, }; alerts_to_open - .push((alert, server.info.send_unreachable_alerts)) + .push((alert, server.config.send_unreachable_alerts)) } (ServerState::NotOk, Some(alert)) => { // update alert err @@ -102,8 +102,10 @@ pub async fn alert_servers( // Close an open alert (ServerState::Ok | ServerState::Disabled, Some(alert)) => { - alert_ids_to_close - .push((alert.clone(), server.info.send_unreachable_alerts)); + alert_ids_to_close.push(( + alert.clone(), + server.config.send_unreachable_alerts, + )); } _ => {} } @@ -119,20 +121,21 @@ pub async fn alert_servers( .as_ref() .and_then(|alerts| alerts.get(&AlertDataVariant::ServerCpu)) .cloned(); - match (health.cpu, cpu_alert) { - (SeverityLevel::Warning | SeverityLevel::Critical, None) => { + match (health.cpu.level, cpu_alert, health.cpu.should_close_alert) + { + (SeverityLevel::Warning | SeverityLevel::Critical, None, _) => { // open alert let alert = Alert { id: Default::default(), ts, resolved: false, resolved_ts: None, - level: health.cpu, + level: health.cpu.level, target: ResourceTarget::Server(server_status.id.clone()), data: AlertData::ServerCpu { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), percentage: server_status .stats .as_ref() @@ -140,41 +143,44 @@ pub async fn alert_servers( .unwrap_or(0.0), }, }; - alerts_to_open.push((alert, server.info.send_cpu_alerts)); + alerts_to_open.push((alert, server.config.send_cpu_alerts)); } ( SeverityLevel::Warning | SeverityLevel::Critical, Some(mut alert), + _, ) => { // modify alert level only if it has increased - if alert.level < health.cpu { - alert.level = health.cpu; + if alert.level < health.cpu.level { + alert.level = health.cpu.level; alert.data = AlertData::ServerCpu { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), percentage: server_status .stats .as_ref() .map(|s| s.cpu_perc as f64) .unwrap_or(0.0), }; - alerts_to_update.push((alert, server.info.send_cpu_alerts)); + alerts_to_update + .push((alert, server.config.send_cpu_alerts)); } } - (SeverityLevel::Ok, Some(alert)) => { + (SeverityLevel::Ok, Some(alert), true) => { let mut alert = alert.clone(); alert.data = AlertData::ServerCpu { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), percentage: server_status .stats .as_ref() .map(|s| s.cpu_perc as f64) .unwrap_or(0.0), }; - alert_ids_to_close.push((alert, server.info.send_cpu_alerts)) + alert_ids_to_close + .push((alert, server.config.send_cpu_alerts)) } _ => {} } @@ -186,20 +192,21 @@ pub async fn alert_servers( .as_ref() .and_then(|alerts| alerts.get(&AlertDataVariant::ServerMem)) .cloned(); - match (health.mem, mem_alert) { - (SeverityLevel::Warning | SeverityLevel::Critical, None) => { + match (health.mem.level, mem_alert, health.mem.should_close_alert) + { + (SeverityLevel::Warning | SeverityLevel::Critical, None, _) => { // open alert let alert = Alert { id: Default::default(), ts, resolved: false, resolved_ts: None, - level: health.mem, + level: health.mem.level, target: ResourceTarget::Server(server_status.id.clone()), data: AlertData::ServerMem { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), total_gb: server_status .stats .as_ref() @@ -212,19 +219,20 @@ pub async fn alert_servers( .unwrap_or(0.0), }, }; - alerts_to_open.push((alert, server.info.send_mem_alerts)); + alerts_to_open.push((alert, server.config.send_mem_alerts)); } ( SeverityLevel::Warning | SeverityLevel::Critical, Some(mut alert), + _, ) => { // modify alert level only if it has increased - if alert.level < health.mem { - alert.level = health.mem; + if alert.level < health.mem.level { + alert.level = health.mem.level; alert.data = AlertData::ServerMem { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), total_gb: server_status .stats .as_ref() @@ -236,15 +244,16 @@ pub async fn alert_servers( .map(|s| s.mem_used_gb) .unwrap_or(0.0), }; - alerts_to_update.push((alert, server.info.send_mem_alerts)); + alerts_to_update + .push((alert, server.config.send_mem_alerts)); } } - (SeverityLevel::Ok, Some(alert)) => { + (SeverityLevel::Ok, Some(alert), true) => { let mut alert = alert.clone(); alert.data = AlertData::ServerMem { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), total_gb: server_status .stats .as_ref() @@ -256,7 +265,8 @@ pub async fn alert_servers( .map(|s| s.mem_used_gb) .unwrap_or(0.0), }; - alert_ids_to_close.push((alert, server.info.send_mem_alerts)) + alert_ids_to_close + .push((alert, server.config.send_mem_alerts)) } _ => {} } @@ -273,8 +283,12 @@ pub async fn alert_servers( .as_ref() .and_then(|alerts| alerts.get(path)) .cloned(); - match (*health, disk_alert) { - (SeverityLevel::Warning | SeverityLevel::Critical, None) => { + match (health.level, disk_alert, health.should_close_alert) { + ( + SeverityLevel::Warning | SeverityLevel::Critical, + None, + _, + ) => { let disk = server_status.stats.as_ref().and_then(|stats| { stats.disks.iter().find(|disk| disk.mount == *path) }); @@ -283,58 +297,60 @@ pub async fn alert_servers( ts, resolved: false, resolved_ts: None, - level: *health, + level: health.level, target: ResourceTarget::Server(server_status.id.clone()), data: AlertData::ServerDisk { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), path: path.to_owned(), total_gb: disk.map(|d| d.total_gb).unwrap_or_default(), used_gb: disk.map(|d| d.used_gb).unwrap_or_default(), }, }; - alerts_to_open.push((alert, server.info.send_disk_alerts)); + alerts_to_open + .push((alert, server.config.send_disk_alerts)); } ( SeverityLevel::Warning | SeverityLevel::Critical, Some(mut alert), + _, ) => { // Disk is persistent, update alert if health changes regardless of direction - if *health != alert.level { + if health.level != alert.level { let disk = server_status.stats.as_ref().and_then(|stats| { stats.disks.iter().find(|disk| disk.mount == *path) }); - alert.level = *health; + alert.level = health.level; alert.data = AlertData::ServerDisk { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), path: path.to_owned(), total_gb: disk.map(|d| d.total_gb).unwrap_or_default(), used_gb: disk.map(|d| d.used_gb).unwrap_or_default(), }; alerts_to_update - .push((alert, server.info.send_disk_alerts)); + .push((alert, server.config.send_disk_alerts)); } } - (SeverityLevel::Ok, Some(alert)) => { + (SeverityLevel::Ok, Some(alert), true) => { let mut alert = alert.clone(); let disk = server_status.stats.as_ref().and_then(|stats| { stats.disks.iter().find(|disk| disk.mount == *path) }); - alert.level = *health; + alert.level = health.level; alert.data = AlertData::ServerDisk { id: server_status.id.clone(), name: server.name.clone(), - region: optional_string(&server.info.region), + region: optional_string(&server.config.region), path: path.to_owned(), total_gb: disk.map(|d| d.total_gb).unwrap_or_default(), used_gb: disk.map(|d| d.used_gb).unwrap_or_default(), }; alert_ids_to_close - .push((alert, server.info.send_disk_alerts)) + .push((alert, server.config.send_disk_alerts)) } _ => {} } @@ -347,7 +363,7 @@ pub async fn alert_servers( let mut alert = alert.clone(); alert.level = SeverityLevel::Ok; alert_ids_to_close - .push((alert, server.info.send_disk_alerts)); + .push((alert, server.config.send_disk_alerts)); } } } diff --git a/bin/core/src/monitor/helpers.rs b/bin/core/src/monitor/helpers.rs index fcd0e0cc1..3bd2a8de8 100644 --- a/bin/core/src/monitor/helpers.rs +++ b/bin/core/src/monitor/helpers.rs @@ -6,7 +6,10 @@ use komodo_client::entities::{ network::NetworkListItem, volume::VolumeListItem, }, repo::Repo, - server::{Server, ServerConfig, ServerHealth, ServerState}, + server::{ + Server, ServerConfig, ServerHealth, ServerHealthState, + ServerState, + }, stack::{ComposeProject, Stack, StackState}, stats::{SingleDiskUsage, SystemStats}, }; @@ -126,6 +129,8 @@ pub async fn insert_server_status( .await; } +const ALERT_PERCENTAGE_THRESHOLD: f32 = 5.0; + fn get_server_health( server: &Server, SystemStats { @@ -148,16 +153,22 @@ fn get_server_health( let mut health = ServerHealth::default(); if cpu_perc >= cpu_critical { - health.cpu = SeverityLevel::Critical + health.cpu.level = SeverityLevel::Critical; } else if cpu_perc >= cpu_warning { - health.cpu = SeverityLevel::Warning + health.cpu.level = SeverityLevel::Warning + } else if *cpu_perc < cpu_warning - ALERT_PERCENTAGE_THRESHOLD { + health.cpu.should_close_alert = true } let mem_perc = 100.0 * mem_used_gb / mem_total_gb; if mem_perc >= *mem_critical { - health.mem = SeverityLevel::Critical + health.mem.level = SeverityLevel::Critical } else if mem_perc >= *mem_warning { - health.mem = SeverityLevel::Warning + health.mem.level = SeverityLevel::Warning + } else if mem_perc + < mem_warning - (ALERT_PERCENTAGE_THRESHOLD as f64) + { + health.mem.should_close_alert = true } for SingleDiskUsage { @@ -168,14 +179,17 @@ fn get_server_health( } in disks { let perc = 100.0 * used_gb / total_gb; - let stats_state = if perc >= *disk_critical { - SeverityLevel::Critical + let mut state = ServerHealthState::default(); + if perc >= *disk_critical { + state.level = SeverityLevel::Critical; } else if perc >= *disk_warning { - SeverityLevel::Warning - } else { - SeverityLevel::Ok + state.level = SeverityLevel::Warning; + } else if perc + < disk_warning - (ALERT_PERCENTAGE_THRESHOLD as f64) + { + state.should_close_alert = true; }; - health.disks.insert(mount.clone(), stats_state); + health.disks.insert(mount.clone(), state); } health diff --git a/bin/migrator/Cargo.toml b/bin/migrator/Cargo.toml deleted file mode 100644 index 6ed02387a..000000000 --- a/bin/migrator/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "migrator" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -# komodo_client.workspace = true -logger.workspace = true -# -# mungos.workspace = true -# -tokio.workspace = true -anyhow.workspace = true -dotenvy.workspace = true -envy.workspace = true -serde.workspace = true -tracing.workspace = true \ No newline at end of file diff --git a/bin/migrator/Dockerfile b/bin/migrator/Dockerfile deleted file mode 100644 index a6872527a..000000000 --- a/bin/migrator/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM rust:1.80.1-bookworm AS builder -WORKDIR /builder -COPY . . -RUN cargo build -p migrator --release - -# Final Image -FROM gcr.io/distroless/cc-debian12 - -COPY --from=builder /builder/target/release/migrator / - -# Label for Ghcr -LABEL org.opencontainers.image.source=https://github.com/mbecker20/komodo -LABEL org.opencontainers.image.description="Database migrator for Komodo version upgrades" -LABEL org.opencontainers.image.licenses=GPL-3.0 - -CMD ["./migrator"] \ No newline at end of file diff --git a/bin/migrator/README.md b/bin/migrator/README.md deleted file mode 100644 index 16627f501..000000000 --- a/bin/migrator/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Migrator - -Performs schema changes on the Komodo database - -## v1.7 - v1.11 migration -Run this before upgrading to latest from versions 1.7 to 1.11. -```sh -docker run --rm --name komodo-migrator \ - --network "host" \ - --env MIGRATION="v1.11" \ - --env TARGET_URI="mongodb://:@
" \ - --env TARGET_DB_NAME="" \ - ghcr.io/mbecker20/komodo_migrator -``` - -## v1.0 - v1.6 migration -Run this before upgrading to latest from versions 1.0 to 1.6. -```sh -docker run --rm --name komodo-migrator \ - --network "host" \ - --env MIGRATION="v1.6" \ - --env TARGET_URI="mongodb://:@
" \ - --env TARGET_DB_NAME="" \ - ghcr.io/mbecker20/komodo_migrator -``` \ No newline at end of file diff --git a/bin/migrator/src/legacy/mod.rs b/bin/migrator/src/legacy/mod.rs deleted file mode 100644 index 789102e51..000000000 --- a/bin/migrator/src/legacy/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[allow(unused)] -pub mod v1_11; diff --git a/bin/migrator/src/legacy/v1_11/build.rs b/bin/migrator/src/legacy/v1_11/build.rs deleted file mode 100644 index 236bc841f..000000000 --- a/bin/migrator/src/legacy/v1_11/build.rs +++ /dev/null @@ -1,261 +0,0 @@ -use komodo_client::entities::{ - build::StandardRegistryConfig, EnvironmentVar, NoData, - SystemCommand, Version, I64, -}; -use serde::{Deserialize, Serialize}; - -use super::resource::Resource; - -pub type Build = Resource; - -impl From for komodo_client::entities::build::Build { - fn from(value: Build) -> Self { - komodo_client::entities::build::Build { - id: value.id, - name: value.name, - description: value.description, - updated_at: value.updated_at, - tags: value.tags, - info: komodo_client::entities::build::BuildInfo { - last_built_at: value.info.last_built_at, - built_hash: None, - built_message: None, - latest_hash: None, - latest_message: None, - }, - config: value.config.into(), - base_permission: Default::default(), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct BuildInfo { - pub last_built_at: I64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BuildConfig { - /// Which builder is used to build the image. - #[serde(default, alias = "builder")] - pub builder_id: String, - - /// The current version of the build. - #[serde(default)] - pub version: Version, - - /// The Github repo used as the source of the build. - #[serde(default)] - pub repo: String, - - /// The branch of the repo. - #[serde(default = "default_branch")] - pub branch: String, - - /// Optionally set a specific commit hash. - #[serde(default)] - pub commit: String, - - /// The github account used to clone (used to access private repos). - /// Empty string is public clone (only public repos). - #[serde(default)] - pub github_account: String, - - /// The optional command run after repo clone and before docker build. - #[serde(default)] - pub pre_build: SystemCommand, - - /// Configuration for the registry to push the built image to. - #[serde(default)] - pub image_registry: ImageRegistry, - - /// The path of the docker build context relative to the root of the repo. - /// Default: "." (the root of the repo). - #[serde(default = "default_build_path")] - pub build_path: String, - - /// The path of the dockerfile relative to the build path. - #[serde(default = "default_dockerfile_path")] - pub dockerfile_path: String, - - /// Whether to skip secret interpolation in the build_args. - #[serde(default)] - pub skip_secret_interp: bool, - - /// Whether to use buildx to build (eg `docker buildx build ...`) - #[serde(default)] - pub use_buildx: bool, - - /// Whether incoming webhooks actually trigger action. - #[serde(default = "default_webhook_enabled")] - pub webhook_enabled: bool, - - /// Any extra docker cli arguments to be included in the build command - #[serde(default)] - pub extra_args: Vec, - - /// Docker build arguments. - /// - /// These values are visible in the final image by running `docker inspect`. - #[serde( - default, - deserialize_with = "komodo_client::entities::env_vars_deserializer" - )] - pub build_args: Vec, - - /// Secret arguments. - /// - /// These values remain hidden in the final image by using - /// docker secret mounts. See ``. - /// - /// The values can be used in RUN commands: - /// ``` - /// RUN --mount=type=secret,id=SECRET_KEY \ - /// SECRET_KEY=$(cat /run/secrets/SECRET_KEY) ... - /// ``` - #[serde( - default, - deserialize_with = "komodo_client::entities::env_vars_deserializer" - )] - pub secret_args: Vec, - - /// Docker labels - #[serde( - default, - deserialize_with = "komodo_client::entities::env_vars_deserializer" - )] - pub labels: Vec, -} - -impl From - for komodo_client::entities::build::BuildConfig -{ - fn from(value: BuildConfig) -> Self { - komodo_client::entities::build::BuildConfig { - builder_id: value.builder_id, - skip_secret_interp: value.skip_secret_interp, - version: komodo_client::entities::Version { - major: value.version.major, - minor: value.version.minor, - patch: value.version.patch, - }, - links: Default::default(), - auto_increment_version: true, - image_name: Default::default(), - image_tag: Default::default(), - git_provider: String::from("github.com"), - git_https: true, - repo: value.repo, - branch: value.branch, - commit: value.commit, - git_account: value.github_account, - pre_build: komodo_client::entities::SystemCommand { - path: value.pre_build.path, - command: value.pre_build.command, - }, - build_path: value.build_path, - dockerfile_path: value.dockerfile_path, - build_args: value - .build_args - .into_iter() - .map(Into::into) - .collect(), - secret_args: Default::default(), - labels: value.labels.into_iter().map(Into::into).collect(), - extra_args: value.extra_args, - use_buildx: value.use_buildx, - webhook_enabled: value.webhook_enabled, - webhook_secret: Default::default(), - image_registry: value.image_registry.into(), - } - } -} - -fn default_branch() -> String { - String::from("main") -} - -fn default_build_path() -> String { - String::from(".") -} - -fn default_dockerfile_path() -> String { - String::from("Dockerfile") -} - -fn default_webhook_enabled() -> bool { - true -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", content = "params")] -pub enum ImageRegistry { - /// Don't push the image to any registry - None(NoData), - /// Push the image to DockerHub - DockerHub(CloudRegistryConfig), - /// Push the image to the Github Container Registry. - /// - /// See [the Github docs](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#pushing-container-images) - /// for information on creating an access token - Ghcr(CloudRegistryConfig), - /// Push the image to Aws Elastic Container Registry - /// - /// The string held in 'params' should match a label of an `aws_ecr_registry` in the core config. - AwsEcr(String), - /// Todo. Will point to a custom "Registry" resource by id - Custom(String), -} - -impl Default for ImageRegistry { - fn default() -> Self { - Self::None(NoData {}) - } -} - -impl From - for komodo_client::entities::build::ImageRegistry -{ - fn from(value: ImageRegistry) -> Self { - match value { - ImageRegistry::None(_) | ImageRegistry::Custom(_) => { - komodo_client::entities::build::ImageRegistry::None(NoData {}) - } - ImageRegistry::DockerHub(params) => { - komodo_client::entities::build::ImageRegistry::Standard( - StandardRegistryConfig { - domain: String::from("docker.io"), - account: params.account, - organization: params.organization, - }, - ) - } - ImageRegistry::Ghcr(params) => { - komodo_client::entities::build::ImageRegistry::Standard( - StandardRegistryConfig { - domain: String::from("ghcr.io"), - account: params.account, - organization: params.organization, - }, - ) - } - ImageRegistry::AwsEcr(label) => { - komodo_client::entities::build::ImageRegistry::None(NoData {}) - } - } - } -} - -#[derive( - Debug, Clone, Default, PartialEq, Serialize, Deserialize, -)] -pub struct CloudRegistryConfig { - /// Specify an account to use with the cloud registry. - #[serde(default)] - pub account: String, - - /// Optional. Specify an organization to push the image under. - /// Empty string means no organization. - #[serde(default)] - pub organization: String, -} diff --git a/bin/migrator/src/legacy/v1_11/deployment.rs b/bin/migrator/src/legacy/v1_11/deployment.rs deleted file mode 100644 index 89da28d6f..000000000 --- a/bin/migrator/src/legacy/v1_11/deployment.rs +++ /dev/null @@ -1,168 +0,0 @@ -use komodo_client::entities::{ - deployment::{ - conversions_deserializer, term_labels_deserializer, Conversion, - DeploymentImage, RestartMode, TerminationSignalLabel, - }, - env_vars_deserializer, EnvironmentVar, TerminationSignal, -}; -use serde::{Deserialize, Serialize}; - -use super::{build::ImageRegistry, resource::Resource}; - -pub type Deployment = Resource; - -impl From - for komodo_client::entities::deployment::Deployment -{ - fn from(value: Deployment) -> Self { - komodo_client::entities::deployment::Deployment { - id: value.id, - name: value.name, - description: value.description, - updated_at: value.updated_at, - tags: value.tags, - info: (), - config: value.config.into(), - base_permission: Default::default(), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct DeploymentConfig { - /// The id of server the deployment is deployed on. - #[serde(default, alias = "server")] - pub server_id: String, - - /// The image which the deployment deploys. - /// Can either be a user inputted image, or a Komodo build. - #[serde(default)] - pub image: DeploymentImage, - - /// Configure the registry used to pull the image from the registry. - /// Used with `docker login`. - /// - /// When using attached build as image source: - /// - If the field is `None` variant, will use the same ImageRegistry config as the build. - /// - Otherwise, it must match the variant of the ImageRegistry build config. - /// - Only the account is used, the organization is not needed here - #[serde(default)] - pub image_registry: ImageRegistry, - - /// Whether to skip secret interpolation into the deployment environment variables. - #[serde(default)] - pub skip_secret_interp: bool, - - /// Whether to redeploy the deployment whenever the attached build finishes. - #[serde(default)] - pub redeploy_on_build: bool, - - /// Whether to send ContainerStateChange alerts for this deployment. - #[serde(default = "default_send_alerts")] - pub send_alerts: bool, - - /// The network attached to the container. - /// Default is `host`. - #[serde(default = "default_network")] - pub network: String, - - /// The restart mode given to the container. - #[serde(default)] - pub restart: RestartMode, - - /// This is interpolated at the end of the `docker run` command, - /// which means they are either passed to the containers inner process, - /// or replaces the container command, depending on use of ENTRYPOINT or CMD in dockerfile. - /// Empty is no command. - #[serde(default)] - pub command: String, - - /// The default termination signal to use to stop the deployment. Defaults to SigTerm (default docker signal). - #[serde(default)] - pub termination_signal: TerminationSignal, - - /// The termination timeout. - #[serde(default = "default_termination_timeout")] - pub termination_timeout: i32, - - /// Extra args which are interpolated into the `docker run` command, - /// and affect the container configuration. - #[serde(default)] - pub extra_args: Vec, - - /// Labels attached to various termination signal options. - /// Used to specify different shutdown functionality depending on the termination signal. - #[serde( - default = "default_term_signal_labels", - deserialize_with = "term_labels_deserializer" - )] - pub term_signal_labels: Vec, - - /// The container port mapping. - /// Irrelevant if container network is `host`. - /// Maps ports on host to ports on container. - #[serde(default, deserialize_with = "conversions_deserializer")] - pub ports: Vec, - - /// The container volume mapping. - /// Maps files / folders on host to files / folders in container. - #[serde(default, deserialize_with = "conversions_deserializer")] - pub volumes: Vec, - - /// The environment variables passed to the container. - #[serde(default, deserialize_with = "env_vars_deserializer")] - pub environment: Vec, - - /// The docker labels given to the container. - #[serde(default, deserialize_with = "env_vars_deserializer")] - pub labels: Vec, -} - -fn default_send_alerts() -> bool { - true -} - -fn default_term_signal_labels() -> Vec { - vec![TerminationSignalLabel::default()] -} - -fn default_termination_timeout() -> i32 { - 10 -} - -fn default_network() -> String { - String::from("host") -} - -impl From - for komodo_client::entities::deployment::DeploymentConfig -{ - fn from(value: DeploymentConfig) -> Self { - komodo_client::entities::deployment::DeploymentConfig { - server_id: value.server_id, - image: value.image, - image_registry_account: match value.image_registry { - ImageRegistry::None(_) - | ImageRegistry::AwsEcr(_) - | ImageRegistry::Custom(_) => String::new(), - ImageRegistry::DockerHub(params) => params.account, - ImageRegistry::Ghcr(params) => params.account, - }, - skip_secret_interp: value.skip_secret_interp, - redeploy_on_build: value.redeploy_on_build, - send_alerts: value.send_alerts, - network: value.network, - restart: value.restart, - command: value.command, - termination_signal: value.termination_signal, - termination_timeout: value.termination_timeout, - extra_args: value.extra_args, - term_signal_labels: value.term_signal_labels, - ports: value.ports, - volumes: value.volumes, - environment: value.environment, - labels: value.labels, - links: Default::default(), - } - } -} diff --git a/bin/migrator/src/legacy/v1_11/mod.rs b/bin/migrator/src/legacy/v1_11/mod.rs deleted file mode 100644 index 26d2e476f..000000000 --- a/bin/migrator/src/legacy/v1_11/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -// use mungos::{init::MongoBuilder, mongodb::Collection}; -// use serde::{Deserialize, Serialize}; - -// pub mod build; -// pub mod deployment; -// pub mod resource; - -// pub struct DbClient { -// pub builds: Collection, -// pub deployments: Collection, -// } - -// impl DbClient { -// pub async fn new( -// legacy_uri: &str, -// legacy_db_name: &str, -// ) -> DbClient { -// let client = MongoBuilder::default() -// .uri(legacy_uri) -// .build() -// .await -// .expect("failed to init legacy mongo client"); -// let db = client.database(legacy_db_name); -// DbClient { -// builds: db.collection("Build"), -// deployments: db.collection("Deployment"), -// } -// } -// } - -// #[derive( -// Serialize, Deserialize, Debug, Clone, Default, PartialEq, -// )] -// pub struct Version { -// pub major: i32, -// pub minor: i32, -// pub patch: i32, -// } - -// #[derive( -// Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, -// )] -// pub struct SystemCommand { -// #[serde(default)] -// pub path: String, -// #[serde(default)] -// pub command: String, -// } diff --git a/bin/migrator/src/legacy/v1_11/resource.rs b/bin/migrator/src/legacy/v1_11/resource.rs deleted file mode 100644 index 9c56a5ca1..000000000 --- a/bin/migrator/src/legacy/v1_11/resource.rs +++ /dev/null @@ -1,54 +0,0 @@ -use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Resource { - /// The Mongo ID of the resource. - /// This field is de/serialized from/to JSON as - /// `{ "_id": { "$oid": "..." }, ...(rest of serialized Resource) }` - #[serde( - default, - rename = "_id", - skip_serializing_if = "String::is_empty", - with = "hex_string_as_object_id" - )] - pub id: String, - - /// The resource name. - /// This is guaranteed unique among others of the same resource type. - pub name: String, - - /// A description for the resource - #[serde(default)] - pub description: String, - - /// When description last updated - #[serde(default)] - pub updated_at: i64, - - /// Tag Ids - #[serde(default)] - pub tags: Vec, - - /// Resource-specific information (not user configurable). - #[serde(default)] - pub info: Info, - - /// Resource-specific configuration. - pub config: Config, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ResourceListItem { - /// The resource id - pub id: String, - /// The resource type, ie `Server` or `Deployment` - // #[serde(rename = "type")] - // pub resource_type: ResourceTargetVariant, - /// The resource name - pub name: String, - /// Tag Ids - pub tags: Vec, - /// Resource specific info - pub info: Info, -} diff --git a/bin/migrator/src/main.rs b/bin/migrator/src/main.rs deleted file mode 100644 index 16165373f..000000000 --- a/bin/migrator/src/main.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![allow(unused)] -#[macro_use] -extern crate tracing; - -use serde::Deserialize; - -mod legacy; -mod migrate; - -#[derive(Deserialize)] -enum Migration { - #[serde(alias = "v1.11")] - V1_11, -} - -#[derive(Deserialize)] -struct Env { - migration: Migration, - target_uri: String, - target_db_name: String, -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - dotenvy::dotenv().ok(); - logger::init(&Default::default())?; - - info!("starting migrator"); - - let env: Env = envy::from_env()?; - - match env.migration { - Migration::V1_11 => { - // let db = legacy::v1_11::DbClient::new( - // &env.target_uri, - // &env.target_db_name, - // ) - // .await; - // migrate::v1_11::migrate_all_in_place(&db).await? - } - } - - info!("finished!"); - - Ok(()) -} diff --git a/bin/migrator/src/migrate/mod.rs b/bin/migrator/src/migrate/mod.rs deleted file mode 100644 index 1e90248b9..000000000 --- a/bin/migrator/src/migrate/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod v1_11; diff --git a/bin/migrator/src/migrate/v1_11.rs b/bin/migrator/src/migrate/v1_11.rs deleted file mode 100644 index 60cd15ae3..000000000 --- a/bin/migrator/src/migrate/v1_11.rs +++ /dev/null @@ -1,70 +0,0 @@ -// use anyhow::Context; -// use komodo_client::entities::{build::Build, deployment::Deployment}; -// use mungos::{ -// find::find_collect, -// mongodb::bson::{doc, to_document}, -// }; - -// use crate::legacy::v1_11; - -// pub async fn migrate_all_in_place( -// db: &v1_11::DbClient, -// ) -> anyhow::Result<()> { -// migrate_builds_in_place(db).await?; -// migrate_deployments_in_place(db).await?; -// Ok(()) -// } - -// pub async fn migrate_builds_in_place( -// db: &v1_11::DbClient, -// ) -> anyhow::Result<()> { -// let builds = find_collect(&db.builds, None, None) -// .await -// .context("failed to get builds")? -// .into_iter() -// .map(Into::into) -// .collect::>(); - -// info!("migrating {} builds...", builds.len()); - -// for build in builds { -// db.builds -// .update_one( -// doc! { "name": &build.name }, -// doc! { "$set": to_document(&build)? }, -// ) -// .await -// .context("failed to insert builds on target")?; -// } - -// info!("builds have been migrated\n"); - -// Ok(()) -// } - -// pub async fn migrate_deployments_in_place( -// db: &v1_11::DbClient, -// ) -> anyhow::Result<()> { -// let deployments = find_collect(&db.deployments, None, None) -// .await -// .context("failed to get deployments")? -// .into_iter() -// .map(Into::into) -// .collect::>(); - -// info!("migrating {} deployments...", deployments.len()); - -// for deployment in deployments { -// db.deployments -// .update_one( -// doc! { "name": &deployment.name }, -// doc! { "$set": to_document(&deployment)? }, -// ) -// .await -// .context("failed to insert deployments on target")?; -// } - -// info!("deployments have been migrated\n"); - -// Ok(()) -// } diff --git a/bin/periphery/src/stats.rs b/bin/periphery/src/stats.rs index 2bb91f3d3..9b350a025 100644 --- a/bin/periphery/src/stats.rs +++ b/bin/periphery/src/stats.rs @@ -91,10 +91,13 @@ impl StatsClient { } pub fn get_system_stats(&self) -> SystemStats { + let total_mem = self.system.total_memory(); + let available_mem = self.system.available_memory(); SystemStats { cpu_perc: self.system.global_cpu_usage(), - mem_used_gb: self.system.used_memory() as f64 / BYTES_PER_GB, - mem_total_gb: self.system.total_memory() as f64 / BYTES_PER_GB, + mem_free_gb: self.system.free_memory() as f64 / BYTES_PER_GB, + mem_used_gb: (total_mem - available_mem) as f64 / BYTES_PER_GB, + mem_total_gb: total_mem as f64 / BYTES_PER_GB, disks: self.get_disks(), polling_rate: self.stats.polling_rate, refresh_ts: self.stats.refresh_ts, diff --git a/changelog.md b/changelog.md deleted file mode 100644 index e7074b69c..000000000 --- a/changelog.md +++ /dev/null @@ -1,109 +0,0 @@ -# Changelog - -## Komodo v1.14 (Sep 2024) -- Renamed the project to **Komodo**. -- Manage docker networks, volumes, and images. -- Manage Containers at the server level, without creating any Deployment. -- Add bulk Start / Restart / Pause actions for all containers on a server. -- Add **Secret** mode to Variables to hide the value in updates / logs - - Secret mode also prevents any non-admin users from retrieving the value from the API. Non admin users will still see the variable name. -- Interpolate Variables / Secrets into everything I could think of - - Deployment / Stack / Repo / Build **extra args**. - - Deployment **command**. - - Build **pre build**. - - Repo **on_clone / on_pull**. -- Added **Hetzner Singapore** datacenter for Hetzner ServerTemplates -- **Removed Google Font** - now just use system local font to avoid any third party calls. - -## Monitor v1.13 - Komodo (Aug 2024) -- This is the first named release, as I think it is really big. The Komodo Dragon is the largest species of Monitor lizard. -- **Deploy docker compose** with the new **Stack** resource. - - Can define the compose file in the UI, or direct Monitor to clone a git repo containing compose files. - - Use webhooks to redeploy the stack on push to the repo - - Manage the environment variables passed to the compose command. -- **Builds** can now be configured with an alternate repository name to push the image under. - -An optional tag can also be configured to be postfixed onto the version, like image:1.13-aarch64. - This helps for pushing alternate build configurations under the same image repo, just under different tags. -- **Repos** can now be "built" using builders. The idea is, you spawn an AWS instance, clone a repo, execute a shell command -(like running a script in the repo), and terminating the instance. The script can build a binary, and push it to some binary repository. -Users will have to manage their own versioning though. -- **High level UI Updates** courtesy of @karamvirsingh98 - -## v1.12 (July 2024) -- Break free of Github dependance. Use other git providers, including self hosted ones. -- Same for Docker registry. You can also now use any docker registry for your images. - -## v1 (Spring 2024) - -- **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 Git 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. - -## v0 (Winter 2022) - -- 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. - -## Pre-versioned releases - -- 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. \ No newline at end of file diff --git a/client/core/rs/README.md b/client/core/rs/README.md index 51edef881..60970f02a 100644 --- a/client/core/rs/README.md +++ b/client/core/rs/README.md @@ -1,4 +1,4 @@ # Komodo -*A system to build and deploy software accross many servers* +*A system to build and deploy software across many servers* Docs: [https://docs.rs/komodo_client/latest/komodo_client](https://docs.rs/komodo_client/latest/komodo_client) \ No newline at end of file diff --git a/client/core/rs/src/api/mod.rs b/client/core/rs/src/api/mod.rs index 6fd695cb6..1ad0d88d8 100644 --- a/client/core/rs/src/api/mod.rs +++ b/client/core/rs/src/api/mod.rs @@ -7,11 +7,14 @@ //! - Path: `/auth`, `/user`, `/read`, `/write`, `/execute` //! - Headers: //! - Content-Type: `application/json` -//! - Authorication: `your_jwt` +//! - Authorization: `your_jwt` //! - X-Api-Key: `your_api_key` //! - X-Api-Secret: `your_api_secret` //! - Use either Authorization *or* X-Api-Key and X-Api-Secret to authenticate requests. //! - Body: JSON specifying the request type (`type`) and the parameters (`params`). +//! +//! You can create API keys for your user, or for a Service User with limited permissions, +//! from the Komodo UI Settings page. //! //! To call the api, construct JSON bodies following //! the schemas given in [read], [mod@write], [execute], and so on. @@ -28,6 +31,18 @@ //! //! The request's parent module (eg. [read], [mod@write]) determines the http path which //! must be used for the requests. For example, requests under [read] are made using http path `/read`. +//! +//! ## Curl Example +//! +//! Putting it all together, here is an example `curl` for [write::UpdateBuild], to update the version: +//! +//! ```text +//! curl --header "Content-Type: application/json" \ +//! --header "X-Api-Key: your_api_key" \ +//! --header "X-Api-Secret: your_api_secret" \ +//! --data '{ "type": "UpdateBuild", "params": { "id": "67076689ed600cfdd52ac637", "config": { "version": "1.15.9" } } }' \ +//! https://komodo.example.com/write +//! ``` //! //! ## Modules //! diff --git a/client/core/rs/src/entities/server.rs b/client/core/rs/src/entities/server.rs index d2616ad44..75ac9e26d 100644 --- a/client/core/rs/src/entities/server.rs +++ b/client/core/rs/src/entities/server.rs @@ -223,13 +223,22 @@ impl Default for ServerConfig { } } +/// The health of a part of the server. +#[typeshare] +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct ServerHealthState { + pub level: SeverityLevel, + /// Whether the health is good enough to close an open alert. + pub should_close_alert: bool, +} + /// Summary of the health of the server. #[typeshare] #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct ServerHealth { - pub cpu: SeverityLevel, - pub mem: SeverityLevel, - pub disks: HashMap, + pub cpu: ServerHealthState, + pub mem: ServerHealthState, + pub disks: HashMap, } /// Current pending actions on the server. diff --git a/client/core/rs/src/entities/stats.rs b/client/core/rs/src/entities/stats.rs index 1d9b7ca11..6f0b18fa6 100644 --- a/client/core/rs/src/entities/stats.rs +++ b/client/core/rs/src/entities/stats.rs @@ -59,7 +59,13 @@ pub struct SystemStatsRecord { pub struct SystemStats { /// Cpu usage percentage pub cpu_perc: f32, - /// Memory used in GB + /// [1.15.9+] + /// Free memory in GB. + /// This is really the 'Free' memory, not the 'Available' memory. + /// It may be different than mem_total_gb - mem_used_gb. + #[serde(default)] + pub mem_free_gb: f64, + /// Used memory in GB. 'Total' - 'Available' (not free) memory. pub mem_used_gb: f64, /// Total memory in GB pub mem_total_gb: f64, diff --git a/client/core/rs/src/lib.rs b/client/core/rs/src/lib.rs index ee1fec823..a1281ad51 100644 --- a/client/core/rs/src/lib.rs +++ b/client/core/rs/src/lib.rs @@ -1,5 +1,5 @@ //! # Komodo -//! *A system to build and deploy software accross many servers* +//! *A system to build and deploy software across many servers* //! //! This is a client library for the Komodo Core API. //! It contains: @@ -35,9 +35,9 @@ use serde::Deserialize; pub mod api; pub mod busy; pub mod entities; +pub mod parser; pub mod ws; -mod parser; mod request; #[derive(Deserialize)] diff --git a/client/core/rs/src/parser.rs b/client/core/rs/src/parser.rs index 3fa20ecd8..0f582f466 100644 --- a/client/core/rs/src/parser.rs +++ b/client/core/rs/src/parser.rs @@ -55,3 +55,51 @@ pub fn parse_key_value_list( }) .collect::>>() } + +/// Parses commands out of multiline string +/// and chains them together with '&&' +/// +/// Supports full line and end of line comments, and escaped newlines. +/// +/// ## Example: +/// ```sh +/// # comments supported +/// sh ./shell1.sh # end of line supported +/// sh ./shell2.sh +/// +/// # escaped newlines supported +/// curl --header "Content-Type: application/json" \ +/// --request POST \ +/// --data '{"key": "value"}' \ +/// https://destination.com +/// +/// # print done +/// echo done +/// ``` +/// becomes +/// ```sh +/// sh ./shell1.sh && sh ./shell2.sh && {long curl command} && echo done +/// ``` +pub fn parse_multiline_command(command: impl AsRef) -> String { + command + .as_ref() + // Remove comments and join back + .split('\n') + .map(str::trim) + .filter(|line| !line.is_empty() && !line.starts_with('#')) + .filter_map(|line| line.split(" #").next()) + .collect::>() + .join("\n") + // Remove escaped newlines + .split(" \\") + .map(str::trim) + .fold(String::new(), |acc, el| acc + " " + el) + // Then final split by newlines and join with && + .split('\n') + .map(str::trim) + .filter(|line| !line.is_empty() && !line.starts_with('#')) + .filter_map(|line| line.split(" #").next()) + .map(str::trim) + .collect::>() + .join(" && ") +} diff --git a/client/core/ts/README.md b/client/core/ts/README.md new file mode 100644 index 000000000..71cd12151 --- /dev/null +++ b/client/core/ts/README.md @@ -0,0 +1,37 @@ +# Komodo + +_A system to build and deploy software across many servers_ + +```sh +npm install komodo_client +``` + +or + +```sh +yarn add komodo_client +``` + +```ts +import { KomodoClient, Types } from "komodo_client"; + +const komodo = KomodoClient("https://demo.komo.do", { + type: "api-key", + params: { + api_key: "your_key", + secret: "your secret", + }, +}); + +const stacks: Types.StackListItem[] = await komodo.read({ + type: "ListStacks", + params: {}, +}); + +const stack: Types.Stack = await komodo.read({ + type: "GetStack", + params: { + stack: stacks[0].name, + } +}); +``` diff --git a/client/core/ts/package.json b/client/core/ts/package.json index ca6943197..288886960 100644 --- a/client/core/ts/package.json +++ b/client/core/ts/package.json @@ -1,16 +1,21 @@ { - "name": "@komodo/client", - "version": "1.0.0", + "name": "komodo_client", + "version": "1.15.9", + "description": "Komodo client package", + "homepage": "https://komo.do", "main": "dist/lib.js", - "license": "MIT", + "license": "GPL-3.0", + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc" }, "dependencies": { - "axios": "^1.4.0" + "axios": "^1.7.7" }, "devDependencies": { - "typescript": "^5.1.6" + "typescript": "^5.6.3" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/client/core/ts/src/lib.ts b/client/core/ts/src/lib.ts index 4c316ad36..108919149 100644 --- a/client/core/ts/src/lib.ts +++ b/client/core/ts/src/lib.ts @@ -5,25 +5,25 @@ import { ReadResponses, UserResponses, WriteResponses, -} from "./responses"; +} from "./responses.js"; import { AuthRequest, ExecuteRequest, ReadRequest, UserRequest, WriteRequest, -} from "./types"; +} from "./types.js"; -export * as Types from "./types"; +export * as Types from "./types.js"; type InitOptions = | { type: "jwt"; params: { jwt: string } } - | { type: "api-key"; params: { api_key: string; secret: string } }; + | { type: "api-key"; params: { key: string; secret: string } }; export function KomodoClient(url: string, options: InitOptions) { const state = { jwt: options.type === "jwt" ? options.params.jwt : undefined, - api_key: options.type === "api-key" ? options.params.api_key : undefined, + key: options.type === "api-key" ? options.params.key : undefined, secret: options.type === "api-key" ? options.params.secret : undefined, }; @@ -32,7 +32,7 @@ export function KomodoClient(url: string, options: InitOptions) { .post(url + path, request, { headers: { Authorization: state.jwt, - "X-API-KEY": state.api_key, + "X-API-KEY": state.key, "X-API-SECRET": state.secret, }, }) diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index 248ee9a0f..9b838a93a 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -1,4 +1,4 @@ -import * as Types from "./types"; +import * as Types from "./types.js"; export type AuthResponses = { GetLoginOptions: Types.GetLoginOptionsResponse; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 3f8af95ba..fbf39229c 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -2243,7 +2243,14 @@ export enum Timelength { export interface SystemStats { /** Cpu usage percentage */ cpu_perc: number; - /** Memory used in GB */ + /** + * [1.15.9+] + * Free memory in GB. + * This is really the 'Free' memory, not the 'Available' memory. + * It may be different than mem_total_gb - mem_used_gb. + */ + mem_free_gb?: number; + /** Used memory in GB. 'Total' - 'Available' (not free) memory. */ mem_used_gb: number; /** Total memory in GB */ mem_total_gb: number; @@ -6435,11 +6442,18 @@ export interface CloneArgs { account?: string; } +/** The health of a part of the server. */ +export interface ServerHealthState { + level: SeverityLevel; + /** Whether the health is good enough to close an open alert. */ + should_close_alert: boolean; +} + /** Summary of the health of the server. */ export interface ServerHealth { - cpu: SeverityLevel; - mem: SeverityLevel; - disks: Record; + cpu: ServerHealthState; + mem: ServerHealthState; + disks: Record; } export enum AwsVolumeType { diff --git a/client/core/ts/yarn.lock b/client/core/ts/yarn.lock index d544f1b5e..91e9572eb 100644 --- a/client/core/ts/yarn.lock +++ b/client/core/ts/yarn.lock @@ -7,12 +7,12 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axios@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" - integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== +axios@^1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: - follow-redirects "^1.15.0" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -28,10 +28,10 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== form-data@^4.0.0: version "4.0.0" @@ -59,7 +59,7 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -typescript@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@^5.6.3: + version "5.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" + integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== diff --git a/docsite/docs/docker-compose.md b/docsite/docs/docker-compose.md index b94b7e1e4..d366b51ce 100644 --- a/docsite/docs/docker-compose.md +++ b/docsite/docs/docker-compose.md @@ -43,10 +43,4 @@ This works by: Just like all other resources with Environments (Deployments, Repos, Builds), Stack Environments support **Variable and Secret interpolation**. Define global variables in the UI and share the values across environments. -::: - -## Using bind mounts - -Repo-based stacks must delete the stack folder before it is able to reclone for the latest repo contents. -Because of this, users should **avoid using relative file paths that are placed inside the repo directory**. -Or better yet, make things simple and use **absolute file paths** or **docker volumes** instead. \ No newline at end of file +::: \ No newline at end of file diff --git a/docsite/src/components/HomepageFeatures/index.tsx b/docsite/src/components/HomepageFeatures/index.tsx index fd551ff2d..cadca6eb7 100644 --- a/docsite/src/components/HomepageFeatures/index.tsx +++ b/docsite/src/components/HomepageFeatures/index.tsx @@ -12,7 +12,7 @@ const FeatureList: FeatureItem[] = [ title: "Automated builds 🛠️", description: ( <> - Build auto versioned docker images from github repos, trigger builds on + Build auto versioned docker images from git repos, trigger builds on git push ), diff --git a/bin/alerter/Cargo.toml b/example/alerter/Cargo.toml similarity index 100% rename from bin/alerter/Cargo.toml rename to example/alerter/Cargo.toml diff --git a/bin/alerter/Dockerfile b/example/alerter/Dockerfile similarity index 100% rename from bin/alerter/Dockerfile rename to example/alerter/Dockerfile diff --git a/bin/alerter/README.md b/example/alerter/README.md similarity index 100% rename from bin/alerter/README.md rename to example/alerter/README.md diff --git a/bin/alerter/src/main.rs b/example/alerter/src/main.rs similarity index 69% rename from bin/alerter/src/main.rs rename to example/alerter/src/main.rs index 0ee1872bf..999e88be9 100644 --- a/bin/alerter/src/main.rs +++ b/example/alerter/src/main.rs @@ -8,6 +8,23 @@ use axum::{routing::post, Json, Router}; use komodo_client::entities::alert::{Alert, SeverityLevel}; use serde::Deserialize; +/// Entrypoint for handling each incoming alert. +async fn handle_incoming_alert(Json(alert): Json) { + if alert.resolved { + info!("Alert Resolved!: {alert:?}"); + return; + } + match alert.level { + SeverityLevel::Ok => info!("{alert:?}"), + SeverityLevel::Warning => warn!("{alert:?}"), + SeverityLevel::Critical => error!("{alert:?}"), + } +} + +/// ======================== +/// Http server boilerplate. +/// ======================== + #[derive(Deserialize)] struct Env { #[serde(default = "default_port")] @@ -30,20 +47,7 @@ async fn app() -> anyhow::Result<()> { info!("v {} | {socket_addr}", env!("CARGO_PKG_VERSION")); - let app = Router::new().route( - "/", - post(|Json(alert): Json| async move { - if alert.resolved { - info!("Alert Resolved!: {alert:?}"); - return; - } - match alert.level { - SeverityLevel::Ok => info!("{alert:?}"), - SeverityLevel::Warning => warn!("{alert:?}"), - SeverityLevel::Critical => error!("{alert:?}"), - } - }), - ); + let app = Router::new().route("/", post(handle_incoming_alert)); let listener = tokio::net::TcpListener::bind(socket_addr) .await diff --git a/bin/update_logger/Cargo.toml b/example/update_logger/Cargo.toml similarity index 100% rename from bin/update_logger/Cargo.toml rename to example/update_logger/Cargo.toml diff --git a/bin/update_logger/Dockerfile b/example/update_logger/Dockerfile similarity index 100% rename from bin/update_logger/Dockerfile rename to example/update_logger/Dockerfile diff --git a/bin/update_logger/src/main.rs b/example/update_logger/src/main.rs similarity index 55% rename from bin/update_logger/src/main.rs rename to example/update_logger/src/main.rs index ded55fa2a..646fea132 100644 --- a/bin/update_logger/src/main.rs +++ b/example/update_logger/src/main.rs @@ -1,7 +1,16 @@ #[macro_use] extern crate tracing; -use komodo_client::KomodoClient; +use komodo_client::{ws::UpdateWsMessage, KomodoClient}; + +/// Entrypoint for handling each incoming update. +async fn handle_incoming_update(update: UpdateWsMessage) { + info!("{update:?}"); +} + +/// ======================== +/// Ws Listener boilerplate. +/// ======================== async fn app() -> anyhow::Result<()> { logger::init(&Default::default())?; @@ -13,12 +22,14 @@ async fn app() -> anyhow::Result<()> { let (mut rx, _) = komodo.subscribe_to_updates(1000, 5)?; loop { - let msg = rx.recv().await; - if let Err(e) = msg { - error!("🚨 recv error | {e:?}"); - break; - } - info!("{msg:?}"); + let update = match rx.recv().await { + Ok(msg) => msg, + Err(e) => { + error!("🚨 recv error | {e:?}"); + break; + } + }; + handle_incoming_update(update).await } Ok(()) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 9f9e7a22b..8220cb111 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -9,7 +9,7 @@ ARG VITE_KOMODO_HOST ENV VITE_KOMODO_HOST ${VITE_KOMODO_HOST} RUN cd client && yarn && yarn build && yarn link -RUN cd frontend && yarn link @komodo/client && yarn && yarn build +RUN cd frontend && yarn link komodo_client && yarn && yarn build ENV PORT 4174 diff --git a/frontend/README.md b/frontend/README.md index c0d16ed55..05f8e8835 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,14 +4,14 @@ Komodo JS stack uses Yarn + Vite + React + Tailwind + shadcn/ui ## Setup Dev Environment -The frontend depends on the local package `@komodo/client` located at `/client/core/ts`. +The frontend depends on the local package `komodo_client` located at `/client/core/ts`. This must first be built and prepared for yarn link. The following command should setup everything up (run with /frontend as working directory): ```sh cd ../client/core/ts && yarn && yarn build && yarn link && \ -cd ../../../frontend && yarn link @komodo/client && yarn +cd ../../../frontend && yarn link komodo_client && yarn ``` You can make a new file `.env.development` (gitignored) which holds: diff --git a/frontend/src/components/alert/details.tsx b/frontend/src/components/alert/details.tsx index 95ead745b..6ea2ef52b 100644 --- a/frontend/src/components/alert/details.tsx +++ b/frontend/src/components/alert/details.tsx @@ -17,7 +17,7 @@ import { text_color_class_by_intention, } from "@lib/color"; import { MonacoEditor } from "@components/monaco"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const AlertDetailsDialog = ({ id }: { id: string }) => { const [open, set] = useState(false); diff --git a/frontend/src/components/alert/index.tsx b/frontend/src/components/alert/index.tsx index 3219dc2e9..229356842 100644 --- a/frontend/src/components/alert/index.tsx +++ b/frontend/src/components/alert/index.tsx @@ -1,7 +1,7 @@ import { Section } from "@components/layouts"; import { alert_level_intention } from "@lib/color"; import { useRead, atomWithStorage } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { useAtom } from "jotai"; import { AlertTriangle } from "lucide-react"; diff --git a/frontend/src/components/alert/table.tsx b/frontend/src/components/alert/table.tsx index f3e26d236..8966b3305 100644 --- a/frontend/src/components/alert/table.tsx +++ b/frontend/src/components/alert/table.tsx @@ -1,4 +1,4 @@ -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DataTable } from "@ui/data-table"; import { AlertLevel } from "."; import { AlertDetailsDialog } from "./details"; diff --git a/frontend/src/components/alert/topbar.tsx b/frontend/src/components/alert/topbar.tsx index 79bc0695c..8c905b090 100644 --- a/frontend/src/components/alert/topbar.tsx +++ b/frontend/src/components/alert/topbar.tsx @@ -11,7 +11,7 @@ import { AlertLevel } from "."; import { ResourceLink } from "@components/resources/common"; import { UsableResource } from "@types"; import { Dialog } from "@ui/dialog"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useState } from "react"; import { AlertDetailsDialogContent } from "./details"; diff --git a/frontend/src/components/config/env_vars.tsx b/frontend/src/components/config/env_vars.tsx index 4bc773fab..e9c26c34b 100644 --- a/frontend/src/components/config/env_vars.tsx +++ b/frontend/src/components/config/env_vars.tsx @@ -1,6 +1,6 @@ import { SecretSelector } from "@components/config/util"; import { useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useToast } from "@ui/use-toast"; export const SecretsSearch = ({ diff --git a/frontend/src/components/config/index.tsx b/frontend/src/components/config/index.tsx index 9b01e9b36..c9de4320d 100644 --- a/frontend/src/components/config/index.tsx +++ b/frontend/src/components/config/index.tsx @@ -5,7 +5,7 @@ import { } from "@components/config/util"; import { Section } from "@components/layouts"; import { MonacoLanguage } from "@components/monaco"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { cn } from "@lib/utils"; import { Button } from "@ui/button"; import { diff --git a/frontend/src/components/config/util.tsx b/frontend/src/components/config/util.tsx index e709ff66d..7d904a5e2 100644 --- a/frontend/src/components/config/util.tsx +++ b/frontend/src/components/config/util.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useCtrlKeyListener, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Select, SelectTrigger, @@ -594,7 +594,7 @@ export function ConfirmUpdate({ file_contents_language, }: ConfirmUpdateProps) { const [open, set] = useState(false); - useCtrlKeyListener("s", () => { + useCtrlKeyListener("Enter", () => { if (open) { onConfirm(); } else { diff --git a/frontend/src/components/export.tsx b/frontend/src/components/export.tsx index ab009839a..86695e457 100644 --- a/frontend/src/components/export.tsx +++ b/frontend/src/components/export.tsx @@ -1,5 +1,5 @@ import { useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Dialog, diff --git a/frontend/src/components/keys/table.tsx b/frontend/src/components/keys/table.tsx index a8e020ce3..4d0dacdb4 100644 --- a/frontend/src/components/keys/table.tsx +++ b/frontend/src/components/keys/table.tsx @@ -1,5 +1,5 @@ import { CopyButton } from "@components/util"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DataTable } from "@ui/data-table"; import { Input } from "@ui/input"; import { ReactNode } from "react"; diff --git a/frontend/src/components/layouts.tsx b/frontend/src/components/layouts.tsx index f5631672c..9773d45eb 100644 --- a/frontend/src/components/layouts.tsx +++ b/frontend/src/components/layouts.tsx @@ -10,7 +10,7 @@ import { DialogTitle, DialogTrigger, } from "@ui/dialog"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ResourceComponents } from "./resources"; import { Card, CardHeader, CardTitle, CardContent, CardFooter } from "@ui/card"; import { ResourceTags } from "./tags"; diff --git a/frontend/src/components/log.tsx b/frontend/src/components/log.tsx index 655c3f5b3..158bcbaf2 100644 --- a/frontend/src/components/log.tsx +++ b/frontend/src/components/log.tsx @@ -1,5 +1,5 @@ import { logToHtml } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@ui/select"; import { ChevronDown } from "lucide-react"; diff --git a/frontend/src/components/monaco.tsx b/frontend/src/components/monaco.tsx index fe191e3cb..d30252081 100644 --- a/frontend/src/components/monaco.tsx +++ b/frontend/src/components/monaco.tsx @@ -31,17 +31,34 @@ export const MonacoEditor = ({ const [editor, setEditor] = useState(null); + useEffect(() => { + if (!editor) return; + + let node = editor.getDomNode(); + if (!node) return; + + const callback = (e: any) => { + if (e.key === "Escape") { + (document.activeElement as any)?.blur?.(); + } + }; + + node.addEventListener("keydown", callback); + return () => node.removeEventListener("keydown", callback); + }, [editor]); + const line_count = value?.split(/\r\n|\r|\n/).length ?? 0; useEffect(() => { if (!editor) return; const contentHeight = line_count * 18 + 30; - const node = editor.getContainerDomNode(); - node.style.height = `${Math.max( + const containerNode = editor.getContainerDomNode(); + + containerNode.style.height = `${Math.max( Math.ceil(contentHeight), MIN_EDITOR_HEIGHT )}px`; - // node.style.height = `${Math.max( + // containerNode.style.height = `${Math.max( // Math.min(Math.ceil(contentHeight), MAX_EDITOR_HEIGHT), // MIN_EDITOR_HEIGHT // )}px`; diff --git a/frontend/src/components/resources/alerter/config/alert_types.tsx b/frontend/src/components/resources/alerter/config/alert_types.tsx index ee867407b..25732e20b 100644 --- a/frontend/src/components/resources/alerter/config/alert_types.tsx +++ b/frontend/src/components/resources/alerter/config/alert_types.tsx @@ -1,5 +1,5 @@ import { ConfigItem } from "@components/config/util"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Badge } from "@ui/badge"; import { Select, diff --git a/frontend/src/components/resources/alerter/config/endpoint.tsx b/frontend/src/components/resources/alerter/config/endpoint.tsx index f349cc31a..d18431249 100644 --- a/frontend/src/components/resources/alerter/config/endpoint.tsx +++ b/frontend/src/components/resources/alerter/config/endpoint.tsx @@ -1,6 +1,6 @@ import { ConfigItem } from "@components/config/util"; import { MonacoEditor } from "@components/monaco"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Select, SelectContent, diff --git a/frontend/src/components/resources/alerter/config/index.tsx b/frontend/src/components/resources/alerter/config/index.tsx index a9bd8520e..5aa1c7a86 100644 --- a/frontend/src/components/resources/alerter/config/index.tsx +++ b/frontend/src/components/resources/alerter/config/index.tsx @@ -1,6 +1,6 @@ import { Config } from "@components/config"; import { useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useState } from "react"; import { EndpointConfig } from "./endpoint"; import { AlertTypeConfig } from "./alert_types"; diff --git a/frontend/src/components/resources/alerter/config/resources.tsx b/frontend/src/components/resources/alerter/config/resources.tsx index d4c4f1127..8df668efc 100644 --- a/frontend/src/components/resources/alerter/config/resources.tsx +++ b/frontend/src/components/resources/alerter/config/resources.tsx @@ -3,7 +3,7 @@ import { ResourceComponents } from "@components/resources"; import { ResourceLink } from "@components/resources/common"; import { useRead } from "@lib/hooks"; import { resource_name } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { UsableResource } from "@types"; import { Button } from "@ui/button"; import { DataTable, SortableHeader } from "@ui/data-table"; diff --git a/frontend/src/components/resources/alerter/index.tsx b/frontend/src/components/resources/alerter/index.tsx index 397165d06..bae4063cb 100644 --- a/frontend/src/components/resources/alerter/index.tsx +++ b/frontend/src/components/resources/alerter/index.tsx @@ -6,7 +6,7 @@ import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { AlerterConfig } from "./config"; import { DeleteResource, NewResource } from "../common"; import { AlerterTable } from "./table"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ResourcePageHeader } from "@components/util"; const useAlerter = (id?: string) => diff --git a/frontend/src/components/resources/alerter/table.tsx b/frontend/src/components/resources/alerter/table.tsx index 4ca35844c..eedef2360 100644 --- a/frontend/src/components/resources/alerter/table.tsx +++ b/frontend/src/components/resources/alerter/table.tsx @@ -1,7 +1,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { ResourceLink } from "../common"; import { TableTags } from "@components/tags"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const AlerterTable = ({ alerters, diff --git a/frontend/src/components/resources/build/actions.tsx b/frontend/src/components/resources/build/actions.tsx index e507719f8..e79852f21 100644 --- a/frontend/src/components/resources/build/actions.tsx +++ b/frontend/src/components/resources/build/actions.tsx @@ -1,6 +1,6 @@ import { ConfirmButton } from "@components/util"; import { useExecute, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Ban, Hammer, Loader2 } from "lucide-react"; import { useBuilder } from "../builder"; diff --git a/frontend/src/components/resources/build/config.tsx b/frontend/src/components/resources/build/config.tsx index 7ab83a0fa..fc05ff6d8 100644 --- a/frontend/src/components/resources/build/config.tsx +++ b/frontend/src/components/resources/build/config.tsx @@ -10,7 +10,7 @@ import { SystemCommand, } from "@components/config/util"; import { useInvalidate, useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Ban, CirclePlus, PlusCircle } from "lucide-react"; import { ReactNode, useState } from "react"; @@ -190,98 +190,6 @@ export const BuildConfig = ({ ), }, }, - { - label: "Webhook", - description: - "Configure your repo provider to send webhooks to Komodo", - components: { - ["Guard" as any]: () => { - if (update.branch ?? config.branch) { - return null; - } - return ( - -
Must configure Branch before webhooks will work.
-
- ); - }, - ["build" as any]: () => ( - - - - ), - webhook_enabled: webhook !== undefined && !webhook.managed, - webhook_secret: { - description: - "Provide a custom webhook secret for this resource, or use the global default.", - placeholder: "Input custom secret", - }, - ["managed" as any]: () => { - const inv = useInvalidate(); - const { toast } = useToast(); - const { mutate: createWebhook, isPending: createPending } = - useWrite("CreateBuildWebhook", { - onSuccess: () => { - toast({ title: "Webhook Created" }); - inv(["GetBuildWebhookEnabled", { build: id }]); - }, - }); - const { mutate: deleteWebhook, isPending: deletePending } = - useWrite("DeleteBuildWebhook", { - onSuccess: () => { - toast({ title: "Webhook Deleted" }); - inv(["GetBuildWebhookEnabled", { build: id }]); - }, - }); - if (!webhook || !webhook.managed) return; - return ( - - {webhook.enabled && ( -
-
- Incoming webhook is{" "} -
- ENABLED -
-
- } - variant="destructive" - onClick={() => deleteWebhook({ build: id })} - loading={deletePending} - disabled={disabled || deletePending} - /> -
- )} - {!webhook.enabled && ( -
-
- Incoming webhook is{" "} -
- DISABLED -
-
- } - onClick={() => createWebhook({ build: id })} - loading={createPending} - disabled={disabled || createPending} - /> -
- )} -
- ); - }, - }, - }, { label: "Links", description: "Add quick links in the resource header", @@ -447,6 +355,98 @@ export const BuildConfig = ({ ), }, }, + { + label: "Webhook", + description: + "Configure your repo provider to send webhooks to Komodo", + components: { + ["Guard" as any]: () => { + if (update.branch ?? config.branch) { + return null; + } + return ( + +
Must configure Branch before webhooks will work.
+
+ ); + }, + ["build" as any]: () => ( + + + + ), + webhook_enabled: webhook !== undefined && !webhook.managed, + webhook_secret: { + description: + "Provide a custom webhook secret for this resource, or use the global default.", + placeholder: "Input custom secret", + }, + ["managed" as any]: () => { + const inv = useInvalidate(); + const { toast } = useToast(); + const { mutate: createWebhook, isPending: createPending } = + useWrite("CreateBuildWebhook", { + onSuccess: () => { + toast({ title: "Webhook Created" }); + inv(["GetBuildWebhookEnabled", { build: id }]); + }, + }); + const { mutate: deleteWebhook, isPending: deletePending } = + useWrite("DeleteBuildWebhook", { + onSuccess: () => { + toast({ title: "Webhook Deleted" }); + inv(["GetBuildWebhookEnabled", { build: id }]); + }, + }); + if (!webhook || !webhook.managed) return; + return ( + + {webhook.enabled && ( +
+
+ Incoming webhook is{" "} +
+ ENABLED +
+
+ } + variant="destructive" + onClick={() => deleteWebhook({ build: id })} + loading={deletePending} + disabled={disabled || deletePending} + /> +
+ )} + {!webhook.enabled && ( +
+
+ Incoming webhook is{" "} +
+ DISABLED +
+
+ } + onClick={() => createWebhook({ build: id })} + loading={createPending} + disabled={disabled || createPending} + /> +
+ )} +
+ ); + }, + }, + }, ], }} /> diff --git a/frontend/src/components/resources/build/index.tsx b/frontend/src/components/resources/build/index.tsx index 640cfdaa7..5a5279fc8 100644 --- a/frontend/src/components/resources/build/index.tsx +++ b/frontend/src/components/resources/build/index.tsx @@ -16,7 +16,7 @@ import { cn } from "@lib/utils"; import { useState } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; import { ResourceComponents } from ".."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DashboardPieChart } from "@pages/home/dashboard"; import { ResourcePageHeader, StatusBadge } from "@components/util"; import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card"; diff --git a/frontend/src/components/resources/build/table.tsx b/frontend/src/components/resources/build/table.tsx index 21349f4f3..0a30973ce 100644 --- a/frontend/src/components/resources/build/table.tsx +++ b/frontend/src/components/resources/build/table.tsx @@ -3,7 +3,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { fmt_version } from "@lib/formatting"; import { ResourceLink } from "../common"; import { BuildComponents } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const BuildTable = ({ builds }: { builds: Types.BuildListItem[] }) => { return ( diff --git a/frontend/src/components/resources/builder/config.tsx b/frontend/src/components/resources/builder/config.tsx index b7cd571eb..002baa554 100644 --- a/frontend/src/components/resources/builder/config.tsx +++ b/frontend/src/components/resources/builder/config.tsx @@ -1,7 +1,7 @@ import { Config } from "@components/config"; import { ConfigItem, ConfigList } from "@components/config/util"; import { useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useState } from "react"; import { ResourceLink, ResourceSelector } from "../common"; import { Button } from "@ui/button"; diff --git a/frontend/src/components/resources/builder/index.tsx b/frontend/src/components/resources/builder/index.tsx index e2754f564..33387a50e 100644 --- a/frontend/src/components/resources/builder/index.tsx +++ b/frontend/src/components/resources/builder/index.tsx @@ -1,6 +1,6 @@ import { NewLayout } from "@components/layouts"; import { useRead, useUser, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { RequiredResourceComponents } from "@types"; import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { Input } from "@ui/input"; diff --git a/frontend/src/components/resources/builder/table.tsx b/frontend/src/components/resources/builder/table.tsx index d69525a27..aff34d6ac 100644 --- a/frontend/src/components/resources/builder/table.tsx +++ b/frontend/src/components/resources/builder/table.tsx @@ -2,7 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { ResourceLink } from "../common"; import { TableTags } from "@components/tags"; import { BuilderInstanceType } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const BuilderTable = ({ builders, diff --git a/frontend/src/components/resources/common.tsx b/frontend/src/components/resources/common.tsx index ffdd11248..6acc7e211 100644 --- a/frontend/src/components/resources/common.tsx +++ b/frontend/src/components/resources/common.tsx @@ -31,7 +31,7 @@ import { ResourceComponents } from "."; import { Input } from "@ui/input"; import { useToast } from "@ui/use-toast"; import { NewLayout } from "@components/layouts"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { filterBySplit, usableResourcePath } from "@lib/utils"; export const ResourceDescription = ({ diff --git a/frontend/src/components/resources/deployment/actions.tsx b/frontend/src/components/resources/deployment/actions.tsx index 4c4a185a9..7c78b9880 100644 --- a/frontend/src/components/resources/deployment/actions.tsx +++ b/frontend/src/components/resources/deployment/actions.tsx @@ -12,7 +12,7 @@ import { useExecute, useInvalidate, useRead, useWrite } from "@lib/hooks"; import { Input } from "@ui/input"; import { useToast } from "@ui/use-toast"; import { useEffect, useState } from "react"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Select, SelectContent, @@ -203,7 +203,7 @@ export const StartStopDeployment = ({ id }: DeploymentId) => { /> ); } - if (state === Types.DeploymentState.Running) { + if (state !== Types.DeploymentState.NotDeployed) { return ; } }; diff --git a/frontend/src/components/resources/deployment/config/components/image.tsx b/frontend/src/components/resources/deployment/config/components/image.tsx index 8b0f40883..e245ab745 100644 --- a/frontend/src/components/resources/deployment/config/components/image.tsx +++ b/frontend/src/components/resources/deployment/config/components/image.tsx @@ -3,7 +3,7 @@ import { ResourceSelector } from "@components/resources/common"; import { fmt_date, fmt_version } from "@lib/formatting"; import { useRead } from "@lib/hooks"; import { filterBySplit } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { CaretSortIcon } from "@radix-ui/react-icons"; import { Command, diff --git a/frontend/src/components/resources/deployment/config/components/restart.tsx b/frontend/src/components/resources/deployment/config/components/restart.tsx index e645469a3..1ab14a495 100644 --- a/frontend/src/components/resources/deployment/config/components/restart.tsx +++ b/frontend/src/components/resources/deployment/config/components/restart.tsx @@ -1,5 +1,5 @@ import { ConfigItem } from "@components/config/util"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Select, SelectContent, diff --git a/frontend/src/components/resources/deployment/config/components/term-signal.tsx b/frontend/src/components/resources/deployment/config/components/term-signal.tsx index c53a19406..9ed32af6e 100644 --- a/frontend/src/components/resources/deployment/config/components/term-signal.tsx +++ b/frontend/src/components/resources/deployment/config/components/term-signal.tsx @@ -1,5 +1,5 @@ import { ConfigItem } from "@components/config/util"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Input } from "@ui/input"; import { Select, diff --git a/frontend/src/components/resources/deployment/config/index.tsx b/frontend/src/components/resources/deployment/config/index.tsx index 51247e6f1..5b0f6f475 100644 --- a/frontend/src/components/resources/deployment/config/index.tsx +++ b/frontend/src/components/resources/deployment/config/index.tsx @@ -1,5 +1,5 @@ import { useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ReactNode, useState } from "react"; import { AccountSelectorConfig, diff --git a/frontend/src/components/resources/deployment/index.tsx b/frontend/src/components/resources/deployment/index.tsx index 66d4ef66f..8b710c772 100644 --- a/frontend/src/components/resources/deployment/index.tsx +++ b/frontend/src/components/resources/deployment/index.tsx @@ -1,5 +1,5 @@ import { useLocalStorage, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { RequiredResourceComponents } from "@types"; import { HardDrive, Rocket, Server } from "lucide-react"; import { cn } from "@lib/utils"; diff --git a/frontend/src/components/resources/deployment/log.tsx b/frontend/src/components/resources/deployment/log.tsx index 78f593c18..c71bb1915 100644 --- a/frontend/src/components/resources/deployment/log.tsx +++ b/frontend/src/components/resources/deployment/log.tsx @@ -1,6 +1,6 @@ import { Section } from "@components/layouts"; import { useLocalStorage, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { RefreshCw, X, AlertOctagon } from "lucide-react"; import { ReactNode, useEffect, useState } from "react"; diff --git a/frontend/src/components/resources/deployment/table.tsx b/frontend/src/components/resources/deployment/table.tsx index 9c96b11dd..fcdaa8e6c 100644 --- a/frontend/src/components/resources/deployment/table.tsx +++ b/frontend/src/components/resources/deployment/table.tsx @@ -1,5 +1,5 @@ import { TableTags } from "@components/tags"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DataTable, SortableHeader } from "@ui/data-table"; import { useRead } from "@lib/hooks"; import { ResourceLink } from "../common"; diff --git a/frontend/src/components/resources/procedure/config.tsx b/frontend/src/components/resources/procedure/config.tsx index 19634204f..0fa3f1307 100644 --- a/frontend/src/components/resources/procedure/config.tsx +++ b/frontend/src/components/resources/procedure/config.tsx @@ -1,7 +1,7 @@ import { ConfigItem } from "@components/config/util"; import { Section } from "@components/layouts"; import { useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Card, CardHeader } from "@ui/card"; import { Input } from "@ui/input"; import { useEffect, useState } from "react"; diff --git a/frontend/src/components/resources/procedure/index.tsx b/frontend/src/components/resources/procedure/index.tsx index b9e8c9883..e7cfbbd40 100644 --- a/frontend/src/components/resources/procedure/index.tsx +++ b/frontend/src/components/resources/procedure/index.tsx @@ -14,7 +14,7 @@ import { stroke_color_class_by_intention, } from "@lib/color"; import { cn } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DashboardPieChart } from "@pages/home/dashboard"; const useProcedure = (id?: string) => diff --git a/frontend/src/components/resources/procedure/table.tsx b/frontend/src/components/resources/procedure/table.tsx index c52abeb8a..bf22aa1d8 100644 --- a/frontend/src/components/resources/procedure/table.tsx +++ b/frontend/src/components/resources/procedure/table.tsx @@ -2,7 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { TableTags } from "@components/tags"; import { ResourceLink } from "../common"; import { ProcedureComponents } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const ProcedureTable = ({ procedures, diff --git a/frontend/src/components/resources/repo/actions.tsx b/frontend/src/components/resources/repo/actions.tsx index ba26390f7..ee4297176 100644 --- a/frontend/src/components/resources/repo/actions.tsx +++ b/frontend/src/components/resources/repo/actions.tsx @@ -8,7 +8,7 @@ import { Loader2, } from "lucide-react"; import { useRepo } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useBuilder } from "../builder"; export const CloneRepo = ({ id }: { id: string }) => { diff --git a/frontend/src/components/resources/repo/config.tsx b/frontend/src/components/resources/repo/config.tsx index 43c517d01..58ada9c72 100644 --- a/frontend/src/components/resources/repo/config.tsx +++ b/frontend/src/components/resources/repo/config.tsx @@ -7,7 +7,7 @@ import { SystemCommand, } from "@components/config/util"; import { useInvalidate, useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useState } from "react"; import { CopyGithubWebhook, ResourceLink, ResourceSelector } from "../common"; import { useToast } from "@ui/use-toast"; diff --git a/frontend/src/components/resources/repo/index.tsx b/frontend/src/components/resources/repo/index.tsx index 975b843a0..9bdb779c0 100644 --- a/frontend/src/components/resources/repo/index.tsx +++ b/frontend/src/components/resources/repo/index.tsx @@ -18,7 +18,7 @@ import { import { cn } from "@lib/utils"; import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card"; import { useServer } from "../server"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DashboardPieChart } from "@pages/home/dashboard"; import { ResourcePageHeader, StatusBadge } from "@components/util"; import { Badge } from "@ui/badge"; diff --git a/frontend/src/components/resources/repo/table.tsx b/frontend/src/components/resources/repo/table.tsx index de94ede74..4c6a3b3bd 100644 --- a/frontend/src/components/resources/repo/table.tsx +++ b/frontend/src/components/resources/repo/table.tsx @@ -2,7 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { ResourceLink } from "../common"; import { TableTags } from "@components/tags"; import { RepoComponents } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const RepoTable = ({ repos }: { repos: Types.RepoListItem[] }) => { return ( diff --git a/frontend/src/components/resources/resource-sync/config.tsx b/frontend/src/components/resources/resource-sync/config.tsx index 6e39601a8..2e78a35a0 100644 --- a/frontend/src/components/resources/resource-sync/config.tsx +++ b/frontend/src/components/resources/resource-sync/config.tsx @@ -6,7 +6,7 @@ import { ProviderSelectorConfig, } from "@components/config/util"; import { useInvalidate, useLocalStorage, useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ReactNode, useState } from "react"; import { CopyGithubWebhook } from "../common"; import { useToast } from "@ui/use-toast"; @@ -148,6 +148,22 @@ export const ResourceSyncConfig = ({ }, }; + const general_common: ConfigComponent = { + label: "General", + components: { + delete: !managed && { + label: "Delete Unmatched Resources", + description: + "Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.", + }, + managed: { + label: "Managed", + description: + "Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.", + }, + }, + }; + const match_tags: ConfigComponent = { label: "Match Tags", description: "Only sync resources matching all of these tags.", @@ -178,16 +194,7 @@ export const ResourceSyncConfig = ({ placeholder="Input resource path" /> ), - delete: !managed && { - label: "Delete Unmatched Resources", - description: - "Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.", - }, - managed: { - label: "Managed", - description: - "Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.", - }, + ...general_common.components, }, }, match_tags, @@ -264,16 +271,7 @@ export const ResourceSyncConfig = ({ placeholder="Input resource path" /> ), - delete: !managed && { - label: "Delete Unmatched Resources", - description: - "Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.", - }, - managed: { - label: "Managed", - description: - "Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.", - }, + ...general_common.components, }, }, match_tags, @@ -471,21 +469,7 @@ export const ResourceSyncConfig = ({ }, }, }, - { - label: "General", - components: { - delete: !managed && { - label: "Delete Unmatched Resources", - description: - "Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.", - }, - managed: { - label: "Managed", - description: - "Enabled managed mode / the 'Commit' button. Commit is the 'reverse' of Execute, and will update the sync file with your configs updated in the UI.", - }, - }, - }, + general_common, match_tags, ], }; diff --git a/frontend/src/components/resources/resource-sync/index.tsx b/frontend/src/components/resources/resource-sync/index.tsx index 6fbb0e592..5ba70ed84 100644 --- a/frontend/src/components/resources/resource-sync/index.tsx +++ b/frontend/src/components/resources/resource-sync/index.tsx @@ -4,7 +4,7 @@ import { Card } from "@ui/card"; import { Clock, FolderSync } from "lucide-react"; import { DeleteResource, NewResource } from "../common"; import { ResourceSyncTable } from "./table"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { CommitSync, ExecuteSync, RefreshSync } from "./actions"; import { border_color_class_by_intention, diff --git a/frontend/src/components/resources/resource-sync/table.tsx b/frontend/src/components/resources/resource-sync/table.tsx index 386028a96..acbf8396f 100644 --- a/frontend/src/components/resources/resource-sync/table.tsx +++ b/frontend/src/components/resources/resource-sync/table.tsx @@ -1,7 +1,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { ResourceLink } from "../common"; import { TableTags } from "@components/tags"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ResourceSyncComponents } from "."; export const ResourceSyncTable = ({ diff --git a/frontend/src/components/resources/server-template/actions.tsx b/frontend/src/components/resources/server-template/actions.tsx index c30b46f95..aece341a5 100644 --- a/frontend/src/components/resources/server-template/actions.tsx +++ b/frontend/src/components/resources/server-template/actions.tsx @@ -15,7 +15,7 @@ import { } from "@ui/dialog"; import { useWebsocketMessages } from "@lib/socket"; import { useNavigate } from "react-router-dom"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const LaunchServer = ({ id }: { id: string }) => { const nav = useNavigate(); diff --git a/frontend/src/components/resources/server-template/config/aws.tsx b/frontend/src/components/resources/server-template/config/aws.tsx index cfc6ba8c5..f3c92c2c6 100644 --- a/frontend/src/components/resources/server-template/config/aws.tsx +++ b/frontend/src/components/resources/server-template/config/aws.tsx @@ -2,7 +2,7 @@ import { Config } from "@components/config"; import { ConfigList } from "@components/config/util"; import { useRead, useWrite } from "@lib/hooks"; import { cn } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Card } from "@ui/card"; import { diff --git a/frontend/src/components/resources/server-template/config/hetzner.tsx b/frontend/src/components/resources/server-template/config/hetzner.tsx index d85907b11..009359485 100644 --- a/frontend/src/components/resources/server-template/config/hetzner.tsx +++ b/frontend/src/components/resources/server-template/config/hetzner.tsx @@ -2,7 +2,7 @@ import { Config } from "@components/config"; import { ConfigItem, ConfigList } from "@components/config/util"; import { useRead, useWrite } from "@lib/hooks"; import { cn, filterBySplit } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Card } from "@ui/card"; import { diff --git a/frontend/src/components/resources/server-template/config/index.tsx b/frontend/src/components/resources/server-template/config/index.tsx index 584e9e922..e9875fc6b 100644 --- a/frontend/src/components/resources/server-template/config/index.tsx +++ b/frontend/src/components/resources/server-template/config/index.tsx @@ -1,7 +1,7 @@ import { useRead } from "@lib/hooks"; import { AwsServerTemplateConfig } from "./aws"; import { HetznerServerTemplateConfig } from "./hetzner"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const ServerTemplateConfig = ({ id }: { id: string }) => { const config = useRead("GetServerTemplate", { server_template: id }).data diff --git a/frontend/src/components/resources/server-template/index.tsx b/frontend/src/components/resources/server-template/index.tsx index 24d3c740e..ce42de309 100644 --- a/frontend/src/components/resources/server-template/index.tsx +++ b/frontend/src/components/resources/server-template/index.tsx @@ -6,7 +6,7 @@ import { ServerTemplateConfig } from "./config"; import { Link, useNavigate } from "react-router-dom"; import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card"; import { useState } from "react"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { NewLayout } from "@components/layouts"; import { Input } from "@ui/input"; import { diff --git a/frontend/src/components/resources/server-template/table.tsx b/frontend/src/components/resources/server-template/table.tsx index 7569e2f36..520b83fb5 100644 --- a/frontend/src/components/resources/server-template/table.tsx +++ b/frontend/src/components/resources/server-template/table.tsx @@ -1,7 +1,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { ResourceLink } from "../common"; import { TableTags } from "@components/tags"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const ServerTemplateTable = ({ serverTemplates, diff --git a/frontend/src/components/resources/server/actions.tsx b/frontend/src/components/resources/server/actions.tsx index 9dc36c38c..c6c8e075b 100644 --- a/frontend/src/components/resources/server/actions.tsx +++ b/frontend/src/components/resources/server/actions.tsx @@ -6,7 +6,7 @@ import { Pen, Scissors } from "lucide-react"; import { useState } from "react"; import { useServer } from "."; import { has_minimum_permissions } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const RenameServer = ({ id }: { id: string }) => { const invalidate = useInvalidate(); diff --git a/frontend/src/components/resources/server/config.tsx b/frontend/src/components/resources/server/config.tsx index 67c03b3fa..16a596813 100644 --- a/frontend/src/components/resources/server/config.tsx +++ b/frontend/src/components/resources/server/config.tsx @@ -1,7 +1,7 @@ import { Config } from "@components/config"; import { ConfigList } from "@components/config/util"; import { useInvalidate, useRead, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ReactNode, useState } from "react"; export const ServerConfig = ({ diff --git a/frontend/src/components/resources/server/hooks.ts b/frontend/src/components/resources/server/hooks.ts index 9b6434c1e..aadd6e73e 100644 --- a/frontend/src/components/resources/server/hooks.ts +++ b/frontend/src/components/resources/server/hooks.ts @@ -1,5 +1,5 @@ import { atomWithStorage } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useAtom } from "jotai"; const statsGranularityAtom = atomWithStorage( diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 2af28e959..ead0c8295 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -1,6 +1,6 @@ import { useExecute, useLocalStorage, useRead, useUser } from "@lib/hooks"; import { cn } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { RequiredResourceComponents } from "@types"; import { Server, diff --git a/frontend/src/components/resources/server/info/index.tsx b/frontend/src/components/resources/server/info/index.tsx index 6596416e6..45889e187 100644 --- a/frontend/src/components/resources/server/info/index.tsx +++ b/frontend/src/components/resources/server/info/index.tsx @@ -2,7 +2,7 @@ import { Section } from "@components/layouts"; import { ReactNode } from "react"; import { Networks } from "./networks"; import { useServer } from ".."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useLocalStorage } from "@lib/hooks"; import { Images } from "./images"; import { Containers } from "./containers"; diff --git a/frontend/src/components/resources/server/stat-chart.tsx b/frontend/src/components/resources/server/stat-chart.tsx index 74e3ea765..80a03f84d 100644 --- a/frontend/src/components/resources/server/stat-chart.tsx +++ b/frontend/src/components/resources/server/stat-chart.tsx @@ -1,6 +1,6 @@ import { hex_color_by_intention } from "@lib/color"; import { useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useMemo } from "react"; import { useStatsGranularity } from "./hooks"; import { Loader2 } from "lucide-react"; diff --git a/frontend/src/components/resources/server/stats.tsx b/frontend/src/components/resources/server/stats.tsx index a776f2dff..dd902d4e9 100644 --- a/frontend/src/components/resources/server/stats.tsx +++ b/frontend/src/components/resources/server/stats.tsx @@ -9,7 +9,7 @@ import { import { Progress } from "@ui/progress"; import { Cpu, Database, Loader2, MemoryStick } from "lucide-react"; import { useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DataTable, SortableHeader } from "@ui/data-table"; import { ReactNode, useMemo, useState } from "react"; import { Input } from "@ui/input"; diff --git a/frontend/src/components/resources/server/table.tsx b/frontend/src/components/resources/server/table.tsx index a7b7bf50f..77f870482 100644 --- a/frontend/src/components/resources/server/table.tsx +++ b/frontend/src/components/resources/server/table.tsx @@ -3,7 +3,7 @@ import { useRead } from "@lib/hooks"; import { DataTable, SortableHeader } from "@ui/data-table"; import { ServerComponents } from "."; import { ResourceLink } from "../common"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useCallback } from "react"; export const ServerTable = ({ diff --git a/frontend/src/components/resources/stack/actions.tsx b/frontend/src/components/resources/stack/actions.tsx index b659fc847..52ebe518e 100644 --- a/frontend/src/components/resources/stack/actions.tsx +++ b/frontend/src/components/resources/stack/actions.tsx @@ -10,7 +10,7 @@ import { Trash, } from "lucide-react"; import { useStack } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useToast } from "@ui/use-toast"; import { useState } from "react"; import { Input } from "@ui/input"; @@ -148,7 +148,7 @@ export const StartStopStack = ({ service?: string; }) => { const stack = useStack(id); - const state = stack?.info.state; + const state = stack?.info.state ?? Types.StackState.Unknown; const { mutate: start, isPending: startPending } = useExecute("StartStack"); const { mutate: stop, isPending: stopPending } = useExecute("StopStack"); const action_state = useRead( @@ -162,18 +162,23 @@ export const StartStopStack = ({ services?.find((s) => s.service === service)?.container?.state) ?? Types.DeploymentState.Unknown; - if (!stack || stack?.info.project_missing) { + if ( + !stack || + [Types.StackState.Down, Types.StackState.Unknown].includes(state) + ) { return null; } - const showStart = - (service && container_state === Types.ContainerStateStatusEnum.Exited) || - state === Types.StackState.Stopped || - state === Types.StackState.Unhealthy; - const showStop = - (service && container_state === Types.ContainerStateStatusEnum.Running) || - state === Types.StackState.Running || - state === Types.StackState.Unhealthy; + const showStart = service + ? (container_state && + container_state !== Types.ContainerStateStatusEnum.Running) ?? + false + : state !== Types.StackState.Running; + const showStop = service + ? (container_state && + container_state !== Types.ContainerStateStatusEnum.Exited) ?? + false + : state !== Types.StackState.Stopped; return ( <> diff --git a/frontend/src/components/resources/stack/config.tsx b/frontend/src/components/resources/stack/config.tsx index 03385865d..706f91eaa 100644 --- a/frontend/src/components/resources/stack/config.tsx +++ b/frontend/src/components/resources/stack/config.tsx @@ -8,7 +8,7 @@ import { ProviderSelectorConfig, SystemCommand, } from "@components/config/util"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useInvalidate, useLocalStorage, useRead, useWrite } from "@lib/hooks"; import { ReactNode, useState } from "react"; import { CopyGithubWebhook, ResourceLink, ResourceSelector } from "../common"; diff --git a/frontend/src/components/resources/stack/index.tsx b/frontend/src/components/resources/stack/index.tsx index ddeaf43e3..345fb9d52 100644 --- a/frontend/src/components/resources/stack/index.tsx +++ b/frontend/src/components/resources/stack/index.tsx @@ -19,7 +19,7 @@ import { import { cn } from "@lib/utils"; import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card"; import { useServer } from "../server"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DeployStack, DestroyStack, diff --git a/frontend/src/components/resources/stack/services.tsx b/frontend/src/components/resources/stack/services.tsx index fd2d20457..c77f7abdc 100644 --- a/frontend/src/components/resources/stack/services.tsx +++ b/frontend/src/components/resources/stack/services.tsx @@ -7,7 +7,7 @@ import { useRead } from "@lib/hooks"; import { cn } from "@lib/utils"; import { DataTable, SortableHeader } from "@ui/data-table"; import { useStack } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ReactNode } from "react"; import { Link } from "react-router-dom"; import { Button } from "@ui/button"; diff --git a/frontend/src/components/resources/stack/table.tsx b/frontend/src/components/resources/stack/table.tsx index 261bd48fa..b30c556bd 100644 --- a/frontend/src/components/resources/stack/table.tsx +++ b/frontend/src/components/resources/stack/table.tsx @@ -3,7 +3,7 @@ import { DataTable, SortableHeader } from "@ui/data-table"; import { ResourceLink } from "../common"; import { TableTags } from "@components/tags"; import { StackComponents } from "."; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useCallback } from "react"; export const StackTable = ({ stacks }: { stacks: Types.StackListItem[] }) => { diff --git a/frontend/src/components/tags/index.tsx b/frontend/src/components/tags/index.tsx index 2d7057087..82496643b 100644 --- a/frontend/src/components/tags/index.tsx +++ b/frontend/src/components/tags/index.tsx @@ -6,7 +6,7 @@ import { useWrite, } from "@lib/hooks"; import { cn, filterBySplit } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Badge } from "@ui/badge"; import { Button } from "@ui/button"; import { diff --git a/frontend/src/components/topbar.tsx b/frontend/src/components/topbar.tsx index 0e09fa72c..850788fac 100644 --- a/frontend/src/components/topbar.tsx +++ b/frontend/src/components/topbar.tsx @@ -242,7 +242,7 @@ const KeyboardShortcuts = () => { Keyboard Shortcuts
- + diff --git a/frontend/src/components/updates/resource.tsx b/frontend/src/components/updates/resource.tsx index 61108746b..dd3ee24a6 100644 --- a/frontend/src/components/updates/resource.tsx +++ b/frontend/src/components/updates/resource.tsx @@ -10,10 +10,10 @@ import { Milestone, } from "lucide-react"; import { Link } from "react-router-dom"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Section } from "@components/layouts"; import { UpdateDetails, UpdateUser } from "./details"; -import { UpdateStatus } from "@komodo/client/dist/types"; +import { UpdateStatus } from "komodo_client/dist/types"; import { fmt_date, fmt_operation, fmt_version } from "@lib/formatting"; import { getUpdateQuery, diff --git a/frontend/src/components/updates/table.tsx b/frontend/src/components/updates/table.tsx index 101351d6a..e9ca522c6 100644 --- a/frontend/src/components/updates/table.tsx +++ b/frontend/src/components/updates/table.tsx @@ -1,5 +1,5 @@ import { fmt_date_with_minutes, fmt_operation } from "@lib/formatting"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DataTable } from "@ui/data-table"; import { useState } from "react"; import { UpdateDetailsInner, UpdateUser } from "./details"; diff --git a/frontend/src/components/updates/topbar.tsx b/frontend/src/components/updates/topbar.tsx index 3036c58f2..6fb97cbb0 100644 --- a/frontend/src/components/updates/topbar.tsx +++ b/frontend/src/components/updates/topbar.tsx @@ -11,7 +11,7 @@ import { Calendar } from "lucide-react"; import { UpdateDetails, UpdateUser } from "./details"; import { ResourceComponents } from "@components/resources"; import { cn, version_is_none } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { fmt_date, fmt_operation, fmt_version } from "@lib/formatting"; import { ResourceName } from "@components/resources/common"; import { UsableResource } from "@types"; diff --git a/frontend/src/components/users/hooks.ts b/frontend/src/components/users/hooks.ts index 5dc2e6205..0b2783799 100644 --- a/frontend/src/components/users/hooks.ts +++ b/frontend/src/components/users/hooks.ts @@ -1,5 +1,5 @@ import { useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { UsableResource } from "@types"; export const useUserTargetPermissions = (user_target: Types.UserTarget) => { diff --git a/frontend/src/components/users/permissions-table.tsx b/frontend/src/components/users/permissions-table.tsx index bc965570e..26b127904 100644 --- a/frontend/src/components/users/permissions-table.tsx +++ b/frontend/src/components/users/permissions-table.tsx @@ -1,5 +1,5 @@ import { useInvalidate, useWrite } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { UsableResource } from "@types"; import { useToast } from "@ui/use-toast"; import { useState } from "react"; diff --git a/frontend/src/components/users/resource-type-permissions.tsx b/frontend/src/components/users/resource-type-permissions.tsx index 76e7a94f5..1e221d872 100644 --- a/frontend/src/components/users/resource-type-permissions.tsx +++ b/frontend/src/components/users/resource-type-permissions.tsx @@ -1,7 +1,7 @@ import { PermissionLevelSelector } from "@components/config/util"; import { useInvalidate, useRead, useWrite } from "@lib/hooks"; import { RESOURCE_TARGETS } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { useToast } from "@ui/use-toast"; import { Card, CardContent, CardHeader } from "@ui/card"; diff --git a/frontend/src/components/users/table.tsx b/frontend/src/components/users/table.tsx index 147b91a54..32f9f30dc 100644 --- a/frontend/src/components/users/table.tsx +++ b/frontend/src/components/users/table.tsx @@ -1,5 +1,5 @@ import { text_color_class_by_intention } from "@lib/color"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { DataTable } from "@ui/data-table"; import { useNavigate } from "react-router-dom"; import { ColumnDef } from "@tanstack/react-table"; diff --git a/frontend/src/components/util.tsx b/frontend/src/components/util.tsx index 027da6422..017787c9a 100644 --- a/frontend/src/components/util.tsx +++ b/frontend/src/components/util.tsx @@ -12,6 +12,7 @@ import { Check, CheckCircle, ChevronDown, + ChevronLeft, ChevronUp, Copy, Database, @@ -19,6 +20,7 @@ import { Loader2, LogOut, Network, + SearchX, Settings, Tags, User, @@ -33,8 +35,8 @@ import { DialogFooter, } from "@ui/dialog"; import { toast, useToast } from "@ui/use-toast"; -import { cn } from "@lib/utils"; -import { Link } from "react-router-dom"; +import { cn, usableResourcePath } from "@lib/utils"; +import { Link, useNavigate } from "react-router-dom"; import { AUTH_TOKEN_STORAGE_KEY } from "@main"; import { Textarea } from "@ui/textarea"; import { Card } from "@ui/card"; @@ -46,13 +48,15 @@ import { stroke_color_class_by_intention, text_color_class_by_intention, } from "@lib/color"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Badge } from "@ui/badge"; import { Section } from "./layouts"; import { DataTable, SortableHeader } from "@ui/data-table"; import { useRead, useUser } from "@lib/hooks"; import { Prune } from "./resources/server/actions"; import { MonacoEditor } from "./monaco"; +import { UsableResource } from "@types"; +import { ResourceComponents } from "./resources"; export const WithLoading = ({ children, @@ -427,7 +431,7 @@ export const TextUpdateMenu = ({ onValueChange={setValue} readOnly={disabled} /> - + {!disabled && ( {confirmButton ? ( @@ -920,3 +924,37 @@ export const TextUpdateMenu2 = ({ ); }; + +export const NotFound = ({ type }: { type: UsableResource | undefined }) => { + const nav = useNavigate(); + const Components = type && ResourceComponents[type]; + return ( +
+ {type && ( +
+ +
+ )} +
+
+
+ {Components ? ( + + ) : ( + + )} +
+

+ {type} {type && " - "} 404 Not Found +

+
+
+
+ ); +}; diff --git a/frontend/src/lib/color.ts b/frontend/src/lib/color.ts index 2e0a5d652..3aaed2199 100644 --- a/frontend/src/lib/color.ts +++ b/frontend/src/lib/color.ts @@ -1,4 +1,4 @@ -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export type ColorIntention = | "Good" diff --git a/frontend/src/lib/formatting.ts b/frontend/src/lib/formatting.ts index 3af811db4..232f82882 100644 --- a/frontend/src/lib/formatting.ts +++ b/frontend/src/lib/formatting.ts @@ -1,4 +1,4 @@ -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const fmt_date = (d: Date) => { const hours = d.getHours(); diff --git a/frontend/src/lib/hooks.ts b/frontend/src/lib/hooks.ts index b05129037..53dd2d179 100644 --- a/frontend/src/lib/hooks.ts +++ b/frontend/src/lib/hooks.ts @@ -1,12 +1,12 @@ import { AUTH_TOKEN_STORAGE_KEY, KOMODO_BASE_URL } from "@main"; -import { KomodoClient as Client, Types } from "@komodo/client"; +import { KomodoClient as Client, Types } from "komodo_client"; import { AuthResponses, ExecuteResponses, ReadResponses, UserResponses, WriteResponses, -} from "@komodo/client/dist/responses"; +} from "komodo_client/dist/responses"; import { UseMutationOptions, UseQueryOptions, @@ -345,6 +345,23 @@ export const useLocalStorage = ( return [state, set]; }; +export const useKeyListener = (listenKey: string, onPress: () => void) => { + useEffect(() => { + const keydown = (e: KeyboardEvent) => { + // This will ignore Shift + listenKey if it is sent from input / textarea + const target = e.target as any; + if (target.matches("input") || target.matches("textarea")) return; + + if (e.key === listenKey) { + e.preventDefault(); + onPress(); + } + }; + document.addEventListener("keydown", keydown); + return () => document.removeEventListener("keydown", keydown); + }); +}; + export const useShiftKeyListener = (listenKey: string, onPress: () => void) => { useEffect(() => { const keydown = (e: KeyboardEvent) => { @@ -362,10 +379,11 @@ export const useShiftKeyListener = (listenKey: string, onPress: () => void) => { }); }; +/** Listens for ctrl (or CMD on mac) + the listenKey */ export const useCtrlKeyListener = (listenKey: string, onPress: () => void) => { useEffect(() => { const keydown = (e: KeyboardEvent) => { - if (e.ctrlKey && e.key === listenKey) { + if ((e.ctrlKey || e.metaKey) && e.key === listenKey) { e.preventDefault(); onPress(); } diff --git a/frontend/src/lib/socket.tsx b/frontend/src/lib/socket.tsx index 73955ca63..749674d28 100644 --- a/frontend/src/lib/socket.tsx +++ b/frontend/src/lib/socket.tsx @@ -1,5 +1,5 @@ import { useInvalidate, useUser } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { toast } from "@ui/use-toast"; import { atom, useAtom } from "jotai"; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index dab4a4762..f736972e5 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,5 +1,5 @@ import { ResourceComponents } from "@components/resources"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { UsableResource } from "@types"; import Convert from "ansi-to-html"; import { type ClassValue, clsx } from "clsx"; diff --git a/frontend/src/pages/alerts.tsx b/frontend/src/pages/alerts.tsx index c8e15a69b..f36beefa3 100644 --- a/frontend/src/pages/alerts.tsx +++ b/frontend/src/pages/alerts.tsx @@ -1,7 +1,7 @@ import { AlertsTable } from "@components/alert/table"; import { Page } from "@components/layouts"; import { useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Select, diff --git a/frontend/src/pages/home/all_resources.tsx b/frontend/src/pages/home/all_resources.tsx index 35ee6da5d..4c507add3 100644 --- a/frontend/src/pages/home/all_resources.tsx +++ b/frontend/src/pages/home/all_resources.tsx @@ -11,7 +11,7 @@ import { useUser, } from "@lib/hooks"; import { cn } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { RequiredResourceComponents, UsableResource } from "@types"; import { Input } from "@ui/input"; import { AlertTriangle } from "lucide-react"; diff --git a/frontend/src/pages/home/dashboard.tsx b/frontend/src/pages/home/dashboard.tsx index 2fe4dc477..0664770fc 100644 --- a/frontend/src/pages/home/dashboard.tsx +++ b/frontend/src/pages/home/dashboard.tsx @@ -14,7 +14,7 @@ import { } from "@lib/color"; import { useNoResources, useRead, useUser } from "@lib/hooks"; import { cn, usableResourcePath } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { UsableResource } from "@types"; import { DataTable, SortableHeader } from "@ui/data-table"; import { AlertTriangle, Box, Circle, History } from "lucide-react"; diff --git a/frontend/src/pages/resource-notifications.tsx b/frontend/src/pages/resource-notifications.tsx index ac20f12cd..32c157bff 100644 --- a/frontend/src/pages/resource-notifications.tsx +++ b/frontend/src/pages/resource-notifications.tsx @@ -1,6 +1,6 @@ import { AlertLevel } from "@components/alert"; import { UpdateDetails } from "@components/updates/details"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ColorIntention, text_color_class_by_intention } from "@lib/color"; import { fmt_operation, fmt_version, fmt_date } from "@lib/formatting"; import { useRead } from "@lib/hooks"; diff --git a/frontend/src/pages/resource.tsx b/frontend/src/pages/resource.tsx index 6bbcb7631..429245f5e 100644 --- a/frontend/src/pages/resource.tsx +++ b/frontend/src/pages/resource.tsx @@ -14,7 +14,7 @@ import { useUser, } from "@lib/hooks"; import { has_minimum_permissions, usableResourcePath } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { UsableResource } from "@types"; import { Button } from "@ui/button"; import { @@ -23,8 +23,9 @@ import { Clapperboard, LinkIcon, } from "lucide-react"; -import { Link, useNavigate, useParams } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; import { ResourceNotifications } from "./resource-notifications"; +import { NotFound } from "@components/util"; export const useEditPermissions = ({ type, id }: Types.ResourceTarget) => { const user = useUser().data; @@ -217,29 +218,3 @@ export const ResourceHeader = ({
); }; - -const NotFound = ({ type }: { type: UsableResource | undefined }) => { - const nav = useNavigate(); - const Components = type && ResourceComponents[type]; - return ( -
- {type && ( -
- -
- )} -
-
-
{Components && }
-

{type ?? "??"} not found

-
-
-
- ); -}; diff --git a/frontend/src/pages/resources.tsx b/frontend/src/pages/resources.tsx index d371d5ec5..b5aa12df0 100644 --- a/frontend/src/pages/resources.tsx +++ b/frontend/src/pages/resources.tsx @@ -9,10 +9,11 @@ import { useSetTitle, useUser, } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Input } from "@ui/input"; import { useState } from "react"; import { Search } from "lucide-react"; +import { NotFound } from "@components/util"; export const Resources = () => { const is_admin = useUser().data?.admin ?? false; @@ -26,12 +27,16 @@ export const Resources = () => { ? "Resource Sync" : type; useSetTitle(name + "s"); - const Components = ResourceComponents[type]; const [search, set] = useState(""); - const resources = useRead(`List${type}s`, {}).data; - const filtered = useFilterResources(resources as any, search); + + const Components = ResourceComponents[type]; + + if (!Components) { + return ; + } + const targets = filtered?.map( (resource): Types.ResourceTarget => ({ type, diff --git a/frontend/src/pages/server-info/container/actions.tsx b/frontend/src/pages/server-info/container/actions.tsx index 940a50361..a431149b4 100644 --- a/frontend/src/pages/server-info/container/actions.tsx +++ b/frontend/src/pages/server-info/container/actions.tsx @@ -1,6 +1,6 @@ import { ActionWithDialog, ConfirmButton } from "@components/util"; import { useExecute, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Pause, Play, RefreshCcw, Square, Trash } from "lucide-react"; import { useNavigate } from "react-router-dom"; diff --git a/frontend/src/pages/server-info/container/index.tsx b/frontend/src/pages/server-info/container/index.tsx index 98ff7b62e..c00ed4ed0 100644 --- a/frontend/src/pages/server-info/container/index.tsx +++ b/frontend/src/pages/server-info/container/index.tsx @@ -15,7 +15,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { ContainerLogs } from "./log"; import { Actions } from "./actions"; import { has_minimum_permissions } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ResourceUpdates } from "@components/updates/resource"; import { container_state_intention } from "@lib/color"; import { UsableResource } from "@types"; diff --git a/frontend/src/pages/server-info/container/log.tsx b/frontend/src/pages/server-info/container/log.tsx index 87932b702..ac9aa07b4 100644 --- a/frontend/src/pages/server-info/container/log.tsx +++ b/frontend/src/pages/server-info/container/log.tsx @@ -1,7 +1,7 @@ import { Section } from "@components/layouts"; import { Log, TailLengthSelector } from "@components/log"; import { useLocalStorage, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Input } from "@ui/input"; import { Switch } from "@ui/switch"; diff --git a/frontend/src/pages/server-info/image.tsx b/frontend/src/pages/server-info/image.tsx index ea6fd9420..035903934 100644 --- a/frontend/src/pages/server-info/image.tsx +++ b/frontend/src/pages/server-info/image.tsx @@ -11,7 +11,7 @@ import { import { fmt_date_with_minutes, format_size_bytes } from "@lib/formatting"; import { useExecute, useRead, useSetTitle } from "@lib/hooks"; import { has_minimum_permissions } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Badge } from "@ui/badge"; import { Button } from "@ui/button"; import { DataTable } from "@ui/data-table"; diff --git a/frontend/src/pages/server-info/network.tsx b/frontend/src/pages/server-info/network.tsx index e57ef9437..295724505 100644 --- a/frontend/src/pages/server-info/network.tsx +++ b/frontend/src/pages/server-info/network.tsx @@ -11,7 +11,7 @@ import { } from "@components/util"; import { useExecute, useRead, useSetTitle } from "@lib/hooks"; import { has_minimum_permissions } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Badge } from "@ui/badge"; import { Button } from "@ui/button"; import { DataTable, SortableHeader } from "@ui/data-table"; diff --git a/frontend/src/pages/server-info/volume.tsx b/frontend/src/pages/server-info/volume.tsx index efcee2f6b..1fd83ce50 100644 --- a/frontend/src/pages/server-info/volume.tsx +++ b/frontend/src/pages/server-info/volume.tsx @@ -11,7 +11,7 @@ import { } from "@components/util"; import { useExecute, useRead, useSetTitle } from "@lib/hooks"; import { has_minimum_permissions } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Badge } from "@ui/badge"; import { Button } from "@ui/button"; import { DataTable } from "@ui/data-table"; diff --git a/frontend/src/pages/settings/profile.tsx b/frontend/src/pages/settings/profile.tsx index afa3a8f21..4a392dd1f 100644 --- a/frontend/src/pages/settings/profile.tsx +++ b/frontend/src/pages/settings/profile.tsx @@ -40,7 +40,7 @@ import { import { KeysTable } from "@components/keys/table"; import { Section } from "@components/layouts"; import { Card, CardHeader } from "@ui/card"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export const Profile = () => { useSetTitle("Profile"); diff --git a/frontend/src/pages/settings/providers.tsx b/frontend/src/pages/settings/providers.tsx index b569dc36c..94de40f67 100644 --- a/frontend/src/pages/settings/providers.tsx +++ b/frontend/src/pages/settings/providers.tsx @@ -6,7 +6,7 @@ import { useUser, useWrite, } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Card } from "@ui/card"; import { DataTable, SortableHeader } from "@ui/data-table"; diff --git a/frontend/src/pages/stack-service/index.tsx b/frontend/src/pages/stack-service/index.tsx index 9cbef6962..98224a0e6 100644 --- a/frontend/src/pages/stack-service/index.tsx +++ b/frontend/src/pages/stack-service/index.tsx @@ -15,7 +15,7 @@ import { } from "@lib/color"; import { useRead, useSetTitle } from "@lib/hooks"; import { cn, has_minimum_permissions } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { ChevronLeft, Clapperboard, Layers2 } from "lucide-react"; import { useNavigate, useParams } from "react-router-dom"; import { StackServiceLogs } from "./log"; diff --git a/frontend/src/pages/stack-service/log.tsx b/frontend/src/pages/stack-service/log.tsx index 346b19e2c..1a858aa58 100644 --- a/frontend/src/pages/stack-service/log.tsx +++ b/frontend/src/pages/stack-service/log.tsx @@ -1,6 +1,6 @@ import { Section } from "@components/layouts"; import { useLocalStorage, useRead } from "@lib/hooks"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { RefreshCw, X, AlertOctagon, ScrollText } from "lucide-react"; import { useEffect, useState } from "react"; diff --git a/frontend/src/pages/updates.tsx b/frontend/src/pages/updates.tsx index 6b4087e9c..9a353bfe4 100644 --- a/frontend/src/pages/updates.tsx +++ b/frontend/src/pages/updates.tsx @@ -3,7 +3,7 @@ import { ResourceComponents } from "@components/resources"; import { UpdatesTable } from "@components/updates/table"; import { useRead, useResourceParamType, useSetTitle } from "@lib/hooks"; import { filterBySplit, RESOURCE_TARGETS } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { CaretSortIcon } from "@radix-ui/react-icons"; import { UsableResource } from "@types"; import { Button } from "@ui/button"; diff --git a/frontend/src/pages/user-group.tsx b/frontend/src/pages/user-group.tsx index 54d11c881..534c21eb2 100644 --- a/frontend/src/pages/user-group.tsx +++ b/frontend/src/pages/user-group.tsx @@ -6,7 +6,7 @@ import { UserTable } from "@components/users/table"; import { ActionWithDialog } from "@components/util"; import { useInvalidate, useRead, useWrite } from "@lib/hooks"; import { filterBySplit } from "@lib/utils"; -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; import { Button } from "@ui/button"; import { Command, diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index 59f5bd531..369b02673 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -1,4 +1,4 @@ -import { Types } from "@komodo/client"; +import { Types } from "komodo_client"; export type UsableResource = Exclude; diff --git a/lib/command/src/lib.rs b/lib/command/src/lib.rs index 5acf423a7..71e20df43 100644 --- a/lib/command/src/lib.rs +++ b/lib/command/src/lib.rs @@ -1,6 +1,9 @@ use std::path::Path; -use komodo_client::entities::{komodo_timestamp, update::Log}; +use komodo_client::{ + entities::{komodo_timestamp, update::Log}, + parser::parse_multiline_command, +}; use run_command::{async_run_command, CommandOutput}; /// Parses commands out of multiline string @@ -23,35 +26,6 @@ pub async fn run_komodo_command( output_into_log(stage, command, start_ts, output) } -/// Parses commands out of multiline string -/// and chains them together with '&&' -/// -/// Supports full line and end of line comments. -/// -/// ## Example: -/// ```sh -/// # comments supported -/// sh ./shell1.sh # end of line supported -/// sh ./shell2.sh -/// # print done -/// echo done -/// ``` -/// becomes -/// ```sh -/// sh ./shell1.sh && sh ./shell2.sh && echo done -/// ``` -pub fn parse_multiline_command(command: impl AsRef) -> String { - command - .as_ref() - .split('\n') - .map(str::trim) - .filter(|line| !line.is_empty() && !line.starts_with('#')) - .filter_map(|line| line.split(" #").next()) - .map(str::trim) - .collect::>() - .join(" && ") -} - pub fn output_into_log( stage: &str, command: String, diff --git a/lib/git/src/commit.rs b/lib/git/src/commit.rs index fac13f459..06d130b87 100644 --- a/lib/git/src/commit.rs +++ b/lib/git/src/commit.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::Context; use command::run_komodo_command; @@ -18,7 +18,8 @@ pub async fn write_commit_file( file: &Path, contents: &str, ) -> anyhow::Result { - let path = repo_dir.join(file); + // Clean up the path by stripping any redundant `/./` + let path = repo_dir.join(file).components().collect::(); if let Some(parent) = path.parent() { let _ = fs::create_dir_all(&parent).await; diff --git a/readme.md b/readme.md index dfb76285b..0808934f2 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,6 @@ there are no warranties. Use at your own risk. - [periphery setup](https://github.com/mbecker20/komodo/blob/main/scripts/readme.md) - [roadmap](https://github.com/mbecker20/komodo/blob/main/roadmap.md) -- [changelog](https://github.com/mbecker20/komodo/blob/main/changelog.md) - [migrator](https://github.com/mbecker20/komodo/blob/main/bin/migrator/README.md) ## Screenshots diff --git a/runfile.toml b/runfile.toml index 64f763c72..777122ad5 100644 --- a/runfile.toml +++ b/runfile.toml @@ -15,6 +15,10 @@ cmd = "yarn dev" path = "frontend" cmd = "yarn build" +[publish-typescript] +path = "client/core/ts" +cmd = "npm publish" + [test-compose] description = "deploys test.compose.yaml" cmd = """ diff --git a/test.compose.yaml b/test.compose.yaml index 5e578b624..30a4c446a 100644 --- a/test.compose.yaml +++ b/test.compose.yaml @@ -14,6 +14,8 @@ services: KOMODO_ENABLE_NEW_USERS: true KOMODO_LOCAL_AUTH: true KOMODO_JWT_SECRET: a_random_secret + volumes: + - repo-cache:/repo-cache periphery: build: @@ -48,6 +50,7 @@ networks: default: {} volumes: + data: + repo-cache: repos: - stacks: - data: \ No newline at end of file + stacks: \ No newline at end of file