From 5088dc5c3c0f20e7f97747de81dcd3b9f10c0c7f Mon Sep 17 00:00:00 2001 From: Maxwell Becker <49575486+mbecker20@users.noreply.github.com> Date: Sun, 13 Oct 2024 18:03:16 -0400 Subject: [PATCH] 1.15.8 (#124) * fix all containers restart and unpause * add CommitSync to Procedure * validate resource query tags causes failure on non exist * files on host init working. match tags fail if tag doesnt exist * intelligent sync match tag selector * fix linting * Wait for user initialize file on host --- Cargo.lock | 26 ++-- Cargo.toml | 2 +- bin/cli/src/exec.rs | 6 + bin/core/src/api/execute/server.rs | 4 +- bin/core/src/api/read/alerter.rs | 16 +- bin/core/src/api/read/build.rs | 29 +++- bin/core/src/api/read/builder.rs | 16 +- bin/core/src/api/read/deployment.rs | 35 ++++- bin/core/src/api/read/mod.rs | 12 +- bin/core/src/api/read/procedure.rs | 18 ++- bin/core/src/api/read/repo.rs | 27 +++- bin/core/src/api/read/server.rs | 47 ++++-- bin/core/src/api/read/server_template.rs | 19 ++- bin/core/src/api/read/stack.rs | 42 +++-- bin/core/src/api/read/sync.rs | 20 ++- bin/core/src/api/read/toml.rs | 20 ++- bin/core/src/api/write/sync.rs | 83 ++++++---- bin/core/src/helpers/procedure.rs | 5 + bin/core/src/helpers/query.rs | 8 + bin/core/src/monitor/alert/mod.rs | 1 + bin/core/src/resource/mod.rs | 32 ++-- bin/core/src/resource/procedure.rs | 10 ++ bin/core/src/sync/file.rs | 22 ++- bin/core/src/sync/resources.rs | 7 + bin/core/src/sync/toml.rs | 7 + bin/periphery/src/api/compose.rs | 23 --- client/core/rs/src/api/execute/mod.rs | 6 +- client/core/rs/src/api/write/sync.rs | 14 +- client/core/rs/src/entities/sync.rs | 11 ++ client/core/ts/src/types.ts | 3 +- .../components/resources/procedure/config.tsx | 11 ++ .../resources/resource-sync/actions.tsx | 6 +- .../resources/resource-sync/config.tsx | 127 +++++++++++++-- .../resources/resource-sync/info.tsx | 147 ++++++++++-------- .../src/components/resources/stack/info.tsx | 118 ++++++++------ frontend/src/lib/utils.ts | 11 ++ lib/git/src/commit.rs | 5 +- 37 files changed, 717 insertions(+), 279 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c6b4c687..9ec1795fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "alerter" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "axum", @@ -943,7 +943,7 @@ dependencies = [ [[package]] name = "command" -version = "1.15.7" +version = "1.15.8" dependencies = [ "komodo_client", "run_command", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.15.7" +version = "1.15.8" dependencies = [ "thiserror", ] @@ -1439,7 +1439,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.15.7" +version = "1.15.8" dependencies = [ "serror", ] @@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "git" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "command", @@ -2192,7 +2192,7 @@ dependencies = [ [[package]] name = "komodo_cli" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "clap", @@ -2208,7 +2208,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "async_timing_util", @@ -2239,7 +2239,7 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "async_timing_util", @@ -2296,7 +2296,7 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "async_timing_util", @@ -2383,7 +2383,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "komodo_client", @@ -2447,7 +2447,7 @@ dependencies = [ [[package]] name = "migrator" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "dotenvy", @@ -3102,7 +3102,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "komodo_client", @@ -4880,7 +4880,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "update_logger" -version = "1.15.7" +version = "1.15.8" dependencies = [ "anyhow", "komodo_client", diff --git a/Cargo.toml b/Cargo.toml index 30ca8c482..bc1b84373 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"] [workspace.package] -version = "1.15.7" +version = "1.15.8" edition = "2021" authors = ["mbecker20 "] license = "GPL-3.0-or-later" diff --git a/bin/cli/src/exec.rs b/bin/cli/src/exec.rs index 37d15a295..2caf67be4 100644 --- a/bin/cli/src/exec.rs +++ b/bin/cli/src/exec.rs @@ -129,6 +129,9 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::RunSync(data) => { println!("{}: {data:?}", "Data".dimmed()) } + Execution::CommitSync(data) => { + println!("{}: {data:?}", "Data".dimmed()) + } Execution::DeployStack(data) => { println!("{}: {data:?}", "Data".dimmed()) } @@ -273,6 +276,9 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::RunSync(request) => { komodo_client().execute(request).await } + Execution::CommitSync(request) => { + komodo_client().write(request).await + } Execution::DeployStack(request) => { komodo_client().execute(request).await } diff --git a/bin/core/src/api/execute/server.rs b/bin/core/src/api/execute/server.rs index 33e21deb0..ade8ccf6f 100644 --- a/bin/core/src/api/execute/server.rs +++ b/bin/core/src/api/execute/server.rs @@ -425,7 +425,7 @@ impl Resolve for State { update_update(update.clone()).await?; let logs = periphery_client(&server)? - .request(api::container::StartAllContainers {}) + .request(api::container::RestartAllContainers {}) .await .context("failed to restart all containers on host")?; @@ -525,7 +525,7 @@ impl Resolve for State { update_update(update.clone()).await?; let logs = periphery_client(&server)? - .request(api::container::StartAllContainers {}) + .request(api::container::UnpauseAllContainers {}) .await .context("failed to unpause all containers on host")?; diff --git a/bin/core/src/api/read/alerter.rs b/bin/core/src/api/read/alerter.rs index 02bcef8cd..836247583 100644 --- a/bin/core/src/api/read/alerter.rs +++ b/bin/core/src/api/read/alerter.rs @@ -12,6 +12,7 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ + helpers::query::get_all_tags, resource, state::{db_client, State}, }; @@ -37,7 +38,12 @@ impl Resolve for State { ListAlerters { query }: ListAlerters, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags).await } } @@ -47,7 +53,13 @@ impl Resolve for State { ListFullAlerters { query }: ListFullAlerters, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } diff --git a/bin/core/src/api/read/build.rs b/bin/core/src/api/read/build.rs index bf80e1aec..06a64aa02 100644 --- a/bin/core/src/api/read/build.rs +++ b/bin/core/src/api/read/build.rs @@ -22,6 +22,7 @@ use resolver_api::Resolve; use crate::{ config::core_config, + helpers::query::get_all_tags, resource, state::{ action_states, build_state_cache, db_client, github_client, State, @@ -49,7 +50,12 @@ impl Resolve for State { ListBuilds { query }: ListBuilds, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags).await } } @@ -59,7 +65,13 @@ impl Resolve for State { ListFullBuilds { query }: ListFullBuilds, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } @@ -94,6 +106,7 @@ impl Resolve for State { let builds = resource::list_full_for_user::( Default::default(), &user, + &[], ) .await .context("failed to get all builds")?; @@ -252,9 +265,15 @@ impl Resolve for State { ListCommonBuildExtraArgs { query }: ListCommonBuildExtraArgs, user: User, ) -> anyhow::Result { - let builds = resource::list_full_for_user::(query, &user) - .await - .context("failed to get resources matching query")?; + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + let builds = + resource::list_full_for_user::(query, &user, &all_tags) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); diff --git a/bin/core/src/api/read/builder.rs b/bin/core/src/api/read/builder.rs index 141a1a26b..0ef46af7d 100644 --- a/bin/core/src/api/read/builder.rs +++ b/bin/core/src/api/read/builder.rs @@ -12,6 +12,7 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ + helpers::query::get_all_tags, resource, state::{db_client, State}, }; @@ -37,7 +38,12 @@ impl Resolve for State { ListBuilders { query }: ListBuilders, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags).await } } @@ -47,7 +53,13 @@ impl Resolve for State { ListFullBuilders { query }: ListFullBuilders, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } diff --git a/bin/core/src/api/read/deployment.rs b/bin/core/src/api/read/deployment.rs index e26631420..697da171f 100644 --- a/bin/core/src/api/read/deployment.rs +++ b/bin/core/src/api/read/deployment.rs @@ -19,7 +19,7 @@ use periphery_client::api; use resolver_api::Resolve; use crate::{ - helpers::periphery_client, + helpers::{periphery_client, query::get_all_tags}, resource, state::{action_states, deployment_status_cache, State}, }; @@ -45,7 +45,13 @@ impl Resolve for State { ListDeployments { query }: ListDeployments, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags) + .await } } @@ -55,7 +61,15 @@ impl Resolve for State { ListFullDeployments { query }: ListFullDeployments, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::( + query, &user, &all_tags, + ) + .await } } @@ -217,6 +231,7 @@ impl Resolve for State { let deployments = resource::list_full_for_user::( Default::default(), &user, + &[], ) .await .context("failed to get deployments from db")?; @@ -254,10 +269,16 @@ impl Resolve for State { ListCommonDeploymentExtraArgs { query }: ListCommonDeploymentExtraArgs, user: User, ) -> anyhow::Result { - let deployments = - resource::list_full_for_user::(query, &user) - .await - .context("failed to get resources matching query")?; + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + let deployments = resource::list_full_for_user::( + query, &user, &all_tags, + ) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index ba82a6d3b..dea802a1c 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -403,12 +403,18 @@ impl Resolve for State { let (builds, repos, syncs) = tokio::try_join!( resource::list_full_for_user::( Default::default(), - &user + &user, + &[] + ), + resource::list_full_for_user::( + Default::default(), + &user, + &[] ), - resource::list_full_for_user::(Default::default(), &user), resource::list_full_for_user::( Default::default(), - &user + &user, + &[] ), )?; diff --git a/bin/core/src/api/read/procedure.rs b/bin/core/src/api/read/procedure.rs index a565e4242..ec1e4961d 100644 --- a/bin/core/src/api/read/procedure.rs +++ b/bin/core/src/api/read/procedure.rs @@ -10,6 +10,7 @@ use komodo_client::{ use resolver_api::Resolve; use crate::{ + helpers::query::get_all_tags, resource, state::{action_states, procedure_state_cache, State}, }; @@ -35,7 +36,13 @@ impl Resolve for State { ListProcedures { query }: ListProcedures, user: User, ) -> anyhow::Result { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags) + .await } } @@ -45,7 +52,13 @@ impl Resolve for State { ListFullProcedures { query }: ListFullProcedures, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } @@ -58,6 +71,7 @@ impl Resolve for State { let procedures = resource::list_full_for_user::( Default::default(), &user, + &[], ) .await .context("failed to get procedures from db")?; diff --git a/bin/core/src/api/read/repo.rs b/bin/core/src/api/read/repo.rs index 86b0d2b6a..5b8f8c976 100644 --- a/bin/core/src/api/read/repo.rs +++ b/bin/core/src/api/read/repo.rs @@ -12,6 +12,7 @@ use resolver_api::Resolve; use crate::{ config::core_config, + helpers::query::get_all_tags, resource, state::{action_states, github_client, repo_state_cache, State}, }; @@ -37,7 +38,12 @@ impl Resolve for State { ListRepos { query }: ListRepos, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags).await } } @@ -47,7 +53,13 @@ impl Resolve for State { ListFullRepos { query }: ListFullRepos, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } @@ -79,10 +91,13 @@ impl Resolve for State { GetReposSummary {}: GetReposSummary, user: User, ) -> anyhow::Result { - let repos = - resource::list_full_for_user::(Default::default(), &user) - .await - .context("failed to get repos from db")?; + let repos = resource::list_full_for_user::( + Default::default(), + &user, + &[], + ) + .await + .context("failed to get repos from db")?; let mut res = GetReposSummaryResponse::default(); diff --git a/bin/core/src/api/read/server.rs b/bin/core/src/api/read/server.rs index cc3e8ef4a..cdc82f27c 100644 --- a/bin/core/src/api/read/server.rs +++ b/bin/core/src/api/read/server.rs @@ -43,7 +43,7 @@ use resolver_api::{Resolve, ResolveToString}; use tokio::sync::Mutex; use crate::{ - helpers::periphery_client, + helpers::{periphery_client, query::get_all_tags}, resource, stack::compose_container_match_regex, state::{action_states, db_client, server_status_cache, State}, @@ -55,9 +55,12 @@ impl Resolve for State { GetServersSummary {}: GetServersSummary, user: User, ) -> anyhow::Result { - let servers = - resource::list_for_user::(Default::default(), &user) - .await?; + let servers = resource::list_for_user::( + Default::default(), + &user, + &[], + ) + .await?; let mut res = GetServersSummaryResponse::default(); for server in servers { res.total += 1; @@ -119,7 +122,12 @@ impl Resolve for State { ListServers { query }: ListServers, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags).await } } @@ -129,7 +137,13 @@ impl Resolve for State { ListFullServers { query }: ListFullServers, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } @@ -374,15 +388,18 @@ impl Resolve for State { ListAllDockerContainers { servers }: ListAllDockerContainers, user: User, ) -> anyhow::Result> { - let servers = - resource::list_for_user::(Default::default(), &user) - .await? - .into_iter() - .filter(|server| { - servers.is_empty() - || servers.contains(&server.id) - || servers.contains(&server.name) - }); + let servers = resource::list_for_user::( + Default::default(), + &user, + &[], + ) + .await? + .into_iter() + .filter(|server| { + servers.is_empty() + || servers.contains(&server.id) + || servers.contains(&server.name) + }); let mut containers = Vec::::new(); diff --git a/bin/core/src/api/read/server_template.rs b/bin/core/src/api/read/server_template.rs index 15489a9e1..b7ddcd844 100644 --- a/bin/core/src/api/read/server_template.rs +++ b/bin/core/src/api/read/server_template.rs @@ -11,6 +11,7 @@ use mungos::mongodb::bson::doc; use resolver_api::Resolve; use crate::{ + helpers::query::get_all_tags, resource, state::{db_client, State}, }; @@ -36,7 +37,13 @@ impl Resolve for State { ListServerTemplates { query }: ListServerTemplates, user: User, ) -> anyhow::Result { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags) + .await } } @@ -46,7 +53,15 @@ impl Resolve for State { ListFullServerTemplates { query }: ListFullServerTemplates, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::( + query, &user, &all_tags, + ) + .await } } diff --git a/bin/core/src/api/read/stack.rs b/bin/core/src/api/read/stack.rs index 8b581019b..9122236f4 100644 --- a/bin/core/src/api/read/stack.rs +++ b/bin/core/src/api/read/stack.rs @@ -17,7 +17,7 @@ use resolver_api::Resolve; use crate::{ config::core_config, - helpers::periphery_client, + helpers::{periphery_client, query::get_all_tags}, resource, stack::get_stack_and_server, state::{action_states, github_client, stack_status_cache, State}, @@ -133,9 +133,15 @@ impl Resolve for State { ListCommonStackExtraArgs { query }: ListCommonStackExtraArgs, user: User, ) -> anyhow::Result { - let stacks = resource::list_full_for_user::(query, &user) - .await - .context("failed to get resources matching query")?; + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + let stacks = + resource::list_full_for_user::(query, &user, &all_tags) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -158,9 +164,15 @@ impl Resolve for State { ListCommonStackBuildExtraArgs { query }: ListCommonStackBuildExtraArgs, user: User, ) -> anyhow::Result { - let stacks = resource::list_full_for_user::(query, &user) - .await - .context("failed to get resources matching query")?; + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + let stacks = + resource::list_full_for_user::(query, &user, &all_tags) + .await + .context("failed to get resources matching query")?; // first collect with guaranteed uniqueness let mut res = HashSet::::new(); @@ -183,7 +195,12 @@ impl Resolve for State { ListStacks { query }: ListStacks, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags).await } } @@ -193,7 +210,13 @@ impl Resolve for State { ListFullStacks { query }: ListFullStacks, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::(query, &user, &all_tags) + .await } } @@ -228,6 +251,7 @@ impl Resolve for State { let stacks = resource::list_full_for_user::( Default::default(), &user, + &[], ) .await .context("failed to get stacks from db")?; diff --git a/bin/core/src/api/read/sync.rs b/bin/core/src/api/read/sync.rs index c35c00948..2606890fa 100644 --- a/bin/core/src/api/read/sync.rs +++ b/bin/core/src/api/read/sync.rs @@ -15,6 +15,7 @@ use resolver_api::Resolve; use crate::{ config::core_config, + helpers::query::get_all_tags, resource, state::{ action_states, github_client, resource_sync_state_cache, State, @@ -42,7 +43,13 @@ impl Resolve for State { ListResourceSyncs { query }: ListResourceSyncs, user: User, ) -> anyhow::Result> { - resource::list_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_for_user::(query, &user, &all_tags) + .await } } @@ -52,7 +59,15 @@ impl Resolve for State { ListFullResourceSyncs { query }: ListFullResourceSyncs, user: User, ) -> anyhow::Result { - resource::list_full_for_user::(query, &user).await + let all_tags = if query.tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + resource::list_full_for_user::( + query, &user, &all_tags, + ) + .await } } @@ -88,6 +103,7 @@ impl Resolve for State { resource::list_full_for_user::( Default::default(), &user, + &[], ) .await .context("failed to get resource_syncs from db")?; diff --git a/bin/core/src/api/read/toml.rs b/bin/core/src/api/read/toml.rs index d61049b97..bfcf07ac2 100644 --- a/bin/core/src/api/read/toml.rs +++ b/bin/core/src/api/read/toml.rs @@ -29,7 +29,9 @@ use mungos::find::find_collect; use resolver_api::Resolve; use crate::{ - helpers::query::{get_id_to_tags, get_user_user_group_ids}, + helpers::query::{ + get_all_tags, get_id_to_tags, get_user_user_group_ids, + }, resource, state::{db_client, State}, sync::{ @@ -46,10 +48,17 @@ impl Resolve for State { ) -> anyhow::Result { let mut targets = Vec::::new(); + let all_tags = if tags.is_empty() { + vec![] + } else { + get_all_tags(None).await? + }; + targets.extend( resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -59,6 +68,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -68,6 +78,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -77,6 +88,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -86,6 +98,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -95,6 +108,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -104,6 +118,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -113,6 +128,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -122,6 +138,7 @@ impl Resolve for State { resource::list_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() @@ -131,6 +148,7 @@ impl Resolve for State { resource::list_full_for_user::( ResourceQuery::builder().tags(tags.clone()).build(), &user, + &all_tags, ) .await? .into_iter() diff --git a/bin/core/src/api/write/sync.rs b/bin/core/src/api/write/sync.rs index 3717c95dc..13a15aa28 100644 --- a/bin/core/src/api/write/sync.rs +++ b/bin/core/src/api/write/sync.rs @@ -219,15 +219,17 @@ impl Resolve for State { &self, CommitSync { sync }: CommitSync, user: User, - ) -> anyhow::Result { + ) -> anyhow::Result { let sync = resource::get_check_permissions::< entities::sync::ResourceSync, >(&sync, &user, PermissionLevel::Write) .await?; + let file_contents_empty = sync.config.file_contents_empty(); + let fresh_sync = !sync.config.files_on_host - && sync.config.file_contents.is_empty() - && sync.config.repo.is_empty(); + && sync.config.repo.is_empty() + && file_contents_empty; if !sync.config.managed && !fresh_sync { return Err(anyhow!( @@ -235,21 +237,30 @@ impl Resolve for State { )); } - let resource_path = sync - .config - .resource_path - .first() - .context("Sync does not have resource path configured.")? - .parse::() - .context("Invalid resource path")?; + // Get this here so it can fail before update created. + let resource_path = + if sync.config.files_on_host || !sync.config.repo.is_empty() { + let resource_path = sync + .config + .resource_path + .first() + .context("Sync does not have resource path configured.")? + .parse::() + .context("Invalid resource path")?; - if resource_path - .extension() - .context("Resource path missing '.toml' extension")? - != "toml" - { - return Err(anyhow!("Resource path missing '.toml' extension")); - } + if resource_path + .extension() + .context("Resource path missing '.toml' extension")? + != "toml" + { + return Err(anyhow!( + "Resource path missing '.toml' extension" + )); + } + Some(resource_path) + } else { + None + }; let res = State .resolve( @@ -266,6 +277,10 @@ impl Resolve for State { update.logs.push(Log::simple("Resources", res.toml.clone())); if sync.config.files_on_host { + let Some(resource_path) = resource_path else { + // Resource path checked above for files_on_host mode. + unreachable!() + }; let file_path = core_config() .sync_directory .join(to_komodo_name(&sync.name)) @@ -284,8 +299,8 @@ impl Resolve for State { format_serror(&e.into()), ); update.finalize(); - add_update(update).await?; - return resource::get::(&sync.name).await; + add_update(update.clone()).await?; + return Ok(update); } else { update.push_simple_log( "Write contents", @@ -293,6 +308,10 @@ impl Resolve for State { ); } } else if !sync.config.repo.is_empty() { + let Some(resource_path) = resource_path else { + // Resource path checked above for repo mode. + unreachable!() + }; // GIT REPO let args: CloneArgs = (&sync).into(); let root = args.unique_path(&core_config().repo_directory)?; @@ -311,8 +330,8 @@ impl Resolve for State { format_serror(&e.into()), ); update.finalize(); - add_update(update).await?; - return resource::get::(&sync.name).await; + add_update(update.clone()).await?; + return Ok(update); } } // =========== @@ -331,22 +350,18 @@ impl Resolve for State { format_serror(&e.into()), ); update.finalize(); - add_update(update).await?; - return resource::get::(&sync.name).await; + add_update(update.clone()).await?; + return Ok(update); } - let res = match State + if let Err(e) = State .resolve(RefreshResourceSyncPending { sync: sync.name }, user) .await { - Ok(sync) => Ok(sync), - Err(e) => { - update.push_error_log( - "Refresh sync pending", - format_serror(&(&e).into()), - ); - Err(e) - } + update.push_error_log( + "Refresh sync pending", + format_serror(&(&e).into()), + ); }; update.finalize(); @@ -365,9 +380,9 @@ impl Resolve for State { .await; refresh_resource_sync_state_cache().await; } - update_update(update).await?; + update_update(update.clone()).await?; - res + Ok(update) } } diff --git a/bin/core/src/helpers/procedure.rs b/bin/core/src/helpers/procedure.rs index 9dfbd57be..f0863cde1 100644 --- a/bin/core/src/helpers/procedure.rs +++ b/bin/core/src/helpers/procedure.rs @@ -706,6 +706,11 @@ async fn execute_execution( ) .await? } + // Exception: This is a write operation. + Execution::CommitSync(req) => State + .resolve(req, user) + .await + .context("Failed at CommitSync")?, Execution::DeployStack(req) => { let req = ExecuteRequest::DeployStack(req); let update = init_execution_update(&req, &user).await?; diff --git a/bin/core/src/helpers/query.rs b/bin/core/src/helpers/query.rs index 9913e75da..f3632d3a9 100644 --- a/bin/core/src/helpers/query.rs +++ b/bin/core/src/helpers/query.rs @@ -201,6 +201,14 @@ pub async fn get_tag_check_owner( Err(anyhow!("user must be tag owner or admin")) } +pub async fn get_all_tags( + filter: impl Into>, +) -> anyhow::Result> { + find_collect(&db_client().tags, filter, None) + .await + .context("failed to query db for tags") +} + pub async fn get_id_to_tags( filter: impl Into>, ) -> anyhow::Result> { diff --git a/bin/core/src/monitor/alert/mod.rs b/bin/core/src/monitor/alert/mod.rs index 5ddd16eb1..639c219dc 100644 --- a/bin/core/src/monitor/alert/mod.rs +++ b/bin/core/src/monitor/alert/mod.rs @@ -42,6 +42,7 @@ async fn get_all_servers_map() -> anyhow::Result<( admin: true, ..Default::default() }, + &[], ) .await .context("failed to get servers from db (in alert_servers)")?; diff --git a/bin/core/src/resource/mod.rs b/bin/core/src/resource/mod.rs index 45739841e..0dfdc271e 100644 --- a/bin/core/src/resource/mod.rs +++ b/bin/core/src/resource/mod.rs @@ -382,8 +382,9 @@ pub async fn get_user_permission_on_resource( pub async fn list_for_user( mut query: ResourceQuery, user: &User, + all_tags: &[Tag], ) -> anyhow::Result> { - validate_resource_query_tags(&mut query).await; + validate_resource_query_tags(&mut query, all_tags)?; let mut filters = Document::new(); query.add_filters(&mut filters); list_for_user_using_document::(filters, user).await @@ -404,8 +405,9 @@ pub async fn list_for_user_using_document( pub async fn list_full_for_user( mut query: ResourceQuery, user: &User, + all_tags: &[Tag], ) -> anyhow::Result>> { - validate_resource_query_tags(&mut query).await; + validate_resource_query_tags(&mut query, all_tags)?; let mut filters = Document::new(); query.add_filters(&mut filters); list_full_for_user_using_document::(filters, user).await @@ -510,7 +512,7 @@ pub async fn create( // Ensure an existing resource with same name doesn't already exist // The database indexing also ensures this but doesn't give a good error message. - if list_full_for_user::(Default::default(), system_user()) + if list_full_for_user::(Default::default(), system_user(), &[]) .await .context("Failed to list all resources for duplicate name check")? .into_iter() @@ -793,14 +795,24 @@ pub async fn delete( // ======= #[instrument(level = "debug")] -pub async fn validate_resource_query_tags< - T: Default + std::fmt::Debug, ->( +pub fn validate_resource_query_tags( query: &mut ResourceQuery, -) { - let futures = query.tags.iter().map(|tag| get_tag(tag)); - let res = join_all(futures).await; - query.tags = res.into_iter().flatten().map(|tag| tag.id).collect(); + all_tags: &[Tag], +) -> anyhow::Result<()> { + query.tags = query + .tags + .iter() + .map(|tag| { + all_tags + .iter() + .find(|t| t.name == *tag || t.id == *tag) + .map(|tag| tag.id.clone()) + .with_context(|| { + format!("No tag found matching name or id: {}", tag) + }) + }) + .collect::>>()?; + Ok(()) } #[instrument] diff --git a/bin/core/src/resource/procedure.rs b/bin/core/src/resource/procedure.rs index 3f922ff96..5584db8d8 100644 --- a/bin/core/src/resource/procedure.rs +++ b/bin/core/src/resource/procedure.rs @@ -494,6 +494,16 @@ async fn validate_config( .await?; params.sync = sync.id; } + Execution::CommitSync(params) => { + // This one is actually a write operation. + let sync = super::get_check_permissions::( + ¶ms.sync, + user, + PermissionLevel::Write, + ) + .await?; + params.sync = sync.id; + } Execution::DeployStack(params) => { let stack = super::get_check_permissions::( ¶ms.stack, diff --git a/bin/core/src/sync/file.rs b/bin/core/src/sync/file.rs index 1ad736f77..cc7105b7c 100644 --- a/bin/core/src/sync/file.rs +++ b/bin/core/src/sync/file.rs @@ -80,9 +80,25 @@ pub fn read_resources( } else { logs.push(Log::simple("Read remote resources", log)); }; + } else if !full_path.exists() { + file_errors.push(SyncFileContents { + resource_path: String::new(), + path: resource_path.display().to_string(), + contents: format_serror( + &anyhow!("Initialize the file to proceed.") + .context(format!("Path {full_path:?} does not exist.")) + .into(), + ), + }); + log.push_str(&format!( + "{}: Resoure path {} does not exist.", + colored("ERROR", Color::Red), + bold(resource_path.display()) + )); + logs.push(Log::error("Read remote resources", log)); } else { log.push_str(&format!( - "{}: Resoure path {} is neither a file nor a directory.", + "{}: Resoure path {} exists, but is neither a file nor a directory.", colored("WARN", Color::Red), bold(resource_path.display()) )); @@ -134,7 +150,7 @@ fn read_resource_file( log.push('\n'); let path_for_view = if let Some(resource_path) = resource_path.as_ref() { - resource_path.join(&file_path) + resource_path.join(file_path) } else { file_path.to_path_buf() }; @@ -164,7 +180,7 @@ fn read_resources_directory( file_errors: &mut Vec, ) -> anyhow::Result<()> { let full_resource_path = root_path.join(resource_path); - let full_path = full_resource_path.join(&curr_path); + let full_path = full_resource_path.join(curr_path); let directory = fs::read_dir(&full_path).with_context(|| { format!("Failed to read directory contents at {full_path:?}") })?; diff --git a/bin/core/src/sync/resources.rs b/bin/core/src/sync/resources.rs index ff100e4f6..949da3831 100644 --- a/bin/core/src/sync/resources.rs +++ b/bin/core/src/sync/resources.rs @@ -588,6 +588,13 @@ impl ResourceSyncTrait for Procedure { .map(|s| s.name.clone()) .unwrap_or_default(); } + Execution::CommitSync(config) => { + config.sync = resources + .syncs + .get(&config.sync) + .map(|s| s.name.clone()) + .unwrap_or_default(); + } Execution::DeployStack(config) => { config.stack = resources .stacks diff --git a/bin/core/src/sync/toml.rs b/bin/core/src/sync/toml.rs index bbdf03ae3..80d1635f6 100644 --- a/bin/core/src/sync/toml.rs +++ b/bin/core/src/sync/toml.rs @@ -687,6 +687,13 @@ impl ToToml for Procedure { .map(|r| &r.name) .unwrap_or(&String::new()), ), + Execution::CommitSync(exec) => exec.sync.clone_from( + all + .syncs + .get(&exec.sync) + .map(|r| &r.name) + .unwrap_or(&String::new()), + ), Execution::DeployStack(exec) => exec.stack.clone_from( all .stacks diff --git a/bin/periphery/src/api/compose.rs b/bin/periphery/src/api/compose.rs index 672e7c2d9..4615008f0 100644 --- a/bin/periphery/src/api/compose.rs +++ b/bin/periphery/src/api/compose.rs @@ -137,24 +137,6 @@ impl Resolve for State { // -const DEFAULT_COMPOSE_CONTENTS: &str = "## 🦎 Hello Komodo 🦎 -services: - hello_world: - image: hello-world - # networks: - # - default - # ports: - # - 3000:3000 - # volumes: - # - data:/data - -# networks: -# default: {} - -# volumes: -# data: -"; - impl Resolve for State { #[instrument( name = "GetComposeContentsOnHost", @@ -186,11 +168,6 @@ impl Resolve for State { for path in file_paths { let full_path = run_directory.join(&path).components().collect::(); - if !full_path.exists() { - fs::write(&full_path, DEFAULT_COMPOSE_CONTENTS) - .await - .context("Failed to init missing compose file on host")?; - } match fs::read_to_string(&full_path).await.with_context(|| { format!( "Failed to read compose file contents at {full_path:?}" diff --git a/client/core/rs/src/api/execute/mod.rs b/client/core/rs/src/api/execute/mod.rs index f1d6adbf0..bd710e166 100644 --- a/client/core/rs/src/api/execute/mod.rs +++ b/client/core/rs/src/api/execute/mod.rs @@ -23,7 +23,10 @@ pub use server_template::*; pub use stack::*; pub use sync::*; -use crate::entities::{NoData, I64}; +use crate::{ + api::write::CommitSync, + entities::{NoData, I64}, +}; pub trait KomodoExecuteRequest: HasResponse {} @@ -101,6 +104,7 @@ pub enum Execution { // SYNC RunSync(RunSync), + CommitSync(CommitSync), // This is a special case, its actually a write operation. // STACK DeployStack(DeployStack), diff --git a/client/core/rs/src/api/write/sync.rs b/client/core/rs/src/api/write/sync.rs index 2d89f590e..1f9a76691 100644 --- a/client/core/rs/src/api/write/sync.rs +++ b/client/core/rs/src/api/write/sync.rs @@ -1,3 +1,4 @@ +use clap::Parser; use derive_empty_traits::EmptyTraits; use resolver_api::derive::Request; use serde::{Deserialize, Serialize}; @@ -121,15 +122,22 @@ pub struct WriteSyncFileContents { // -/// Commits matching resources updated configuration to the target resource sync. Response: [Update] +/// Exports matching resources, and writes to the target sync's resource file. Response: [Update] /// /// Note. Will fail if the Sync is not `managed`. #[typeshare] #[derive( - Serialize, Deserialize, Debug, Clone, Request, EmptyTraits, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Request, + EmptyTraits, + Parser, )] #[empty_traits(KomodoWriteRequest)] -#[response(ResourceSync)] +#[response(Update)] pub struct CommitSync { /// Id or name #[serde(alias = "id", alias = "name")] diff --git a/client/core/rs/src/entities/sync.rs b/client/core/rs/src/entities/sync.rs index e239c1f41..ab9c4e785 100644 --- a/client/core/rs/src/entities/sync.rs +++ b/client/core/rs/src/entities/sync.rs @@ -265,6 +265,17 @@ impl ResourceSyncConfig { pub fn builder() -> ResourceSyncConfigBuilder { ResourceSyncConfigBuilder::default() } + + /// Checks for empty file contents, ignoring whitespace / comments. + pub fn file_contents_empty(&self) -> bool { + self + .file_contents + .split('\n') + .map(str::trim) + .filter(|line| !line.is_empty() && !line.starts_with('#')) + .count() + == 0 + } } fn default_git_provider() -> String { diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 003d62f75..3f8af95ba 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -864,6 +864,7 @@ export type Execution = | { type: "PruneBuildx", params: PruneBuildx } | { type: "PruneSystem", params: PruneSystem } | { type: "RunSync", params: RunSync } + | { type: "CommitSync", params: CommitSync } | { type: "DeployStack", params: DeployStack } | { type: "DeployStackIfChanged", params: DeployStackIfChanged } | { type: "StartStack", params: StartStack } @@ -6084,7 +6085,7 @@ export interface WriteSyncFileContents { } /** - * Commits matching resources updated configuration to the target resource sync. Response: [Update] + * Exports matching resources, and writes to the target sync's resource file. Response: [Update] * * Note. Will fail if the Sync is not `managed`. */ diff --git a/frontend/src/components/resources/procedure/config.tsx b/frontend/src/components/resources/procedure/config.tsx index e41dc6891..19634204f 100644 --- a/frontend/src/components/resources/procedure/config.tsx +++ b/frontend/src/components/resources/procedure/config.tsx @@ -1059,6 +1059,17 @@ const TARGET_COMPONENTS: ExecutionConfigs = { /> ), }, + CommitSync: { + params: { sync: "" }, + Component: ({ params, setParams, disabled }) => ( + setParams({ sync: id })} + disabled={disabled} + /> + ), + }, Sleep: { params: { duration_ms: 0 }, diff --git a/frontend/src/components/resources/resource-sync/actions.tsx b/frontend/src/components/resources/resource-sync/actions.tsx index a87876f66..0f45f5df0 100644 --- a/frontend/src/components/resources/resource-sync/actions.tsx +++ b/frontend/src/components/resources/resource-sync/actions.tsx @@ -1,6 +1,6 @@ import { ActionButton, ActionWithDialog } from "@components/util"; import { useExecute, useInvalidate, useRead, useWrite } from "@lib/hooks"; -import { sync_no_changes } from "@lib/utils"; +import { file_contents_empty, sync_no_changes } from "@lib/utils"; import { useEditPermissions } from "@pages/resource"; import { NotebookPen, RefreshCcw, SquarePlay } from "lucide-react"; import { useFullResourceSync } from "."; @@ -68,7 +68,7 @@ export const CommitSync = ({ id }: { id: string }) => { const freshSync = !sync.config?.files_on_host && - !sync.config?.file_contents && + file_contents_empty(sync.config?.file_contents) && !sync.config?.repo; if (!freshSync && (!sync.config?.managed || sync_no_changes(sync))) { @@ -97,6 +97,4 @@ export const CommitSync = ({ id }: { id: string }) => { /> ); } - - }; diff --git a/frontend/src/components/resources/resource-sync/config.tsx b/frontend/src/components/resources/resource-sync/config.tsx index 55d4f6da2..6e39601a8 100644 --- a/frontend/src/components/resources/resource-sync/config.tsx +++ b/frontend/src/components/resources/resource-sync/config.tsx @@ -12,7 +12,7 @@ import { CopyGithubWebhook } from "../common"; import { useToast } from "@ui/use-toast"; import { text_color_class_by_intention } from "@lib/color"; import { ConfirmButton, ShowHideButton } from "@components/util"; -import { Ban, CirclePlus } from "lucide-react"; +import { Ban, CirclePlus, MinusCircle, SearchX, Tag } from "lucide-react"; import { MonacoEditor } from "@components/monaco"; import { Select, @@ -21,6 +21,17 @@ import { SelectTrigger, SelectValue, } from "@ui/select"; +import { filterBySplit } from "@lib/utils"; +import { Button } from "@ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@ui/command"; type SyncMode = "UI Defined" | "Files On Server" | "Git Repo" | undefined; const SYNC_MODES: SyncMode[] = ["UI Defined", "Files On Server", "Git Repo"]; @@ -139,19 +150,9 @@ export const ResourceSyncConfig = ({ const match_tags: ConfigComponent = { label: "Match Tags", + description: "Only sync resources matching all of these tags.", components: { - match_tags: (values, set) => ( - - ), + match_tags: (values, set) => , }, }; @@ -507,3 +508,103 @@ export const ResourceSyncConfig = ({ /> ); }; + +const MatchTags = ({ + tags, + set, +}: { + tags: string[]; + set: (update: Partial) => void; +}) => { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + const all_tags = useRead("ListTags", {}).data; + const filtered = filterBySplit(all_tags, search, (item) => item.name); + return ( +
+ { + setSearch(""); + setOpen(open); + }} + > + + + + + + + + + No Tags Found + + + + + {filtered + ?.filter((tag) => !tags.includes(tag.name)) + .map((tag) => ( + { + set({ match_tags: [...tags, tag.name] }); + setSearch(""); + setOpen(false); + }} + className="flex items-center justify-between cursor-pointer" + > +
{tag.name}
+
+ ))} +
+
+
+
+
+ + + set({ match_tags: tags.filter((name) => name !== tag) }) + } + /> +
+ ); +}; + +const MatchTagsTags = ({ + tags, + onBadgeClick, +}: { + tags?: string[]; + onBadgeClick: (tag: string) => void; +}) => { + return ( + <> + {tags?.map((tag) => ( + + ))} + + ); +}; diff --git a/frontend/src/components/resources/resource-sync/info.tsx b/frontend/src/components/resources/resource-sync/info.tsx index 92c67a2c9..b531f249d 100644 --- a/frontend/src/components/resources/resource-sync/info.tsx +++ b/frontend/src/components/resources/resource-sync/info.tsx @@ -2,7 +2,7 @@ import { Section } from "@components/layouts"; import { ReactNode, useState } from "react"; import { Card, CardContent, CardHeader } from "@ui/card"; import { useFullResourceSync } from "."; -import { updateLogToHtml } from "@lib/utils"; +import { cn, updateLogToHtml } from "@lib/utils"; import { MonacoEditor } from "@components/monaco"; import { useEditPermissions } from "@pages/resource"; import { useWrite } from "@lib/hooks"; @@ -10,7 +10,7 @@ import { useToast } from "@ui/use-toast"; import { Button } from "@ui/button"; import { FilePlus, History } from "lucide-react"; import { ConfirmUpdate } from "@components/config/util"; -import { ConfirmButton } from "@components/util"; +import { ConfirmButton, ShowHideButton } from "@components/util"; export const ResourceSyncInfo = ({ id, @@ -20,6 +20,7 @@ export const ResourceSyncInfo = ({ titleOther: ReactNode; }) => { const [edits, setEdits] = useState>({}); + const [show, setShow] = useState>({}); const { canWrite } = useEditPermissions({ type: "ResourceSync", id }); const { toast } = useToast(); const { mutateAsync, isPending } = useWrite("WriteSyncFileContents", { @@ -34,12 +35,15 @@ export const ResourceSyncInfo = ({ const file_on_host = sync?.config?.files_on_host ?? false; const git_repo = sync?.config?.repo ? true : false; const canEdit = canWrite && (file_on_host || git_repo); - const editFileCallback = (path: string) => (contents: string) => - setEdits({ ...edits, [path]: contents }); + const editFileCallback = (keyPath: string) => (contents: string) => + setEdits({ ...edits, [keyPath]: contents }); const latest_contents = sync?.info?.remote_contents; const latest_errors = sync?.info?.remote_errors; + // Contents will be default hidden if there is more than 2 file editor to show + const default_show_contents = !latest_contents || latest_contents.length < 3; + return (
{/* Errors */} @@ -65,7 +69,7 @@ export const ResourceSyncInfo = ({ {canEdit && ( } onClick={() => { if (sync) { @@ -95,69 +99,86 @@ export const ResourceSyncInfo = ({ {/* Update latest contents */} {latest_contents && latest_contents.length > 0 && - latest_contents.map((content) => ( - - -
- {content.resource_path && ( - <> -
-
Folder:
- {content.resource_path} -
-
|
- + latest_contents.map((content) => { + const keyPath = content.resource_path + "/" + content.path; + const showContents = show[keyPath] ?? default_show_contents; + return ( + + -
File:
- {content.path} + > +
+ {content.resource_path && ( + <> +
+
Folder:
+ {content.resource_path} +
+
|
+ + )} +
+
File:
+ {content.path} +
-
- {canEdit && ( -
- - { - if (sync) { - return await mutateAsync({ - sync: sync.name, - resource_path: content.resource_path ?? "", - file_path: content.path, - contents: edits[content.path]!, - }).then(() => - setEdits({ ...edits, [content.path]: undefined }) - ); - } - }} - disabled={!edits[content.path]} - language="toml" - loading={isPending} +
+ {canEdit && ( + <> + + { + if (sync) { + return await mutateAsync({ + sync: sync.name, + resource_path: content.resource_path ?? "", + file_path: content.path, + contents: edits[keyPath]!, + }).then(() => + setEdits({ ...edits, [keyPath]: undefined }) + ); + } + }} + disabled={!edits[keyPath]} + language="toml" + loading={isPending} + /> + + )} + setShow({ ...show, [keyPath]: val })} />
+ + {showContents && ( + + + )} - - - - - - ))} + + ); + })}
); }; diff --git a/frontend/src/components/resources/stack/info.tsx b/frontend/src/components/resources/stack/info.tsx index 12496ef2f..85c0cbcd6 100644 --- a/frontend/src/components/resources/stack/info.tsx +++ b/frontend/src/components/resources/stack/info.tsx @@ -2,7 +2,7 @@ import { Section } from "@components/layouts"; import { ReactNode, useState } from "react"; import { Card, CardContent, CardHeader } from "@ui/card"; import { useFullStack } from "."; -import { updateLogToHtml } from "@lib/utils"; +import { cn, updateLogToHtml } from "@lib/utils"; import { MonacoEditor } from "@components/monaco"; import { useEditPermissions } from "@pages/resource"; import { ConfirmUpdate } from "@components/config/util"; @@ -10,7 +10,7 @@ import { useWrite } from "@lib/hooks"; import { Button } from "@ui/button"; import { FilePlus, History } from "lucide-react"; import { useToast } from "@ui/use-toast"; -import { ConfirmButton } from "@components/util"; +import { ConfirmButton, ShowHideButton } from "@components/util"; import { DEFAULT_STACK_FILE_CONTENTS } from "./config"; export const StackInfo = ({ @@ -21,6 +21,7 @@ export const StackInfo = ({ titleOther: ReactNode; }) => { const [edits, setEdits] = useState>({}); + const [show, setShow] = useState>({}); const { canWrite } = useEditPermissions({ type: "Stack", id }); const { toast } = useToast(); const { mutateAsync, isPending } = useWrite("WriteStackFileContents", { @@ -72,6 +73,9 @@ export const StackInfo = ({ const latest_contents = stack?.info?.remote_contents; const latest_errors = stack?.info?.remote_errors; + // Contents will be default hidden if there is more than 2 file editor to show + const default_show_contents = !latest_contents || latest_contents.length < 3; + return (
{/* Errors */} @@ -86,7 +90,7 @@ export const StackInfo = ({ {canEdit && ( } onClick={() => { if (stack) { @@ -188,57 +192,73 @@ export const StackInfo = ({ {/* Update latest contents */} {latest_contents && latest_contents.length > 0 && - latest_contents.map((content) => ( - - -
-
File:
- {content.path} -
- {canEdit && ( + latest_contents.map((content) => { + const showContents = show[content.path] ?? default_show_contents; + return ( + + +
+
File:
+ {content.path} +
- - { - if (stack) { - return await mutateAsync({ - stack: stack.name, - file_path: content.path, - contents: edits[content.path]!, - }).then(() => + {canEdit && ( + <> + + { + if (stack) { + return await mutateAsync({ + stack: stack.name, + file_path: content.path, + contents: edits[content.path]!, + }).then(() => + setEdits({ ...edits, [content.path]: undefined }) + ); + } + }} + disabled={!edits[content.path]} + language="yaml" + loading={isPending} + /> + + )} + setShow({ ...show, [content.path]: val })} />
+
+ {showContents && ( + + + )} -
- - - -
- ))} + + ); + })}
); }; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index dbd83c16a..dab4a4762 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -253,3 +253,14 @@ export const extract_registry_domain = (image_name: string) => { return "docker.io"; } }; + +/** Checks file contents empty, not including whitespace / comments */ +export const file_contents_empty = (contents?: string) => { + if (!contents) return true; + return ( + contents + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length !== 0 && !line.startsWith("#")).length === 0 + ); +}; diff --git a/lib/git/src/commit.rs b/lib/git/src/commit.rs index 29954bf73..fac13f459 100644 --- a/lib/git/src/commit.rs +++ b/lib/git/src/commit.rs @@ -60,7 +60,7 @@ pub async fn commit_file_inner( file: &Path, ) { ensure_global_git_config_set().await; - + let add_log = run_komodo_command( "add files", repo_dir, @@ -146,8 +146,7 @@ pub async fn commit_all(repo_dir: &Path, message: &str) -> GitRes { }; let push_log = - run_komodo_command("push", repo_dir, format!("git push -f")) - .await; + run_komodo_command("push", repo_dir, "git push -f").await; res.logs.push(push_log); res