From 4d401d7f200fd56d091b9ad4489f1e84f9aeb223 Mon Sep 17 00:00:00 2001 From: Maxwell Becker <49575486+mbecker20@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:08:51 -0700 Subject: [PATCH] 1.18.1 (#566) * 1.18.1 * improve stack header / all resource links * disable build config selector * clean up deployment header * update build header * builder header * update repo header * start adding repo links from api * implement list item repo link * clean up fe * gen client * repo links across the board * include state tracking buffer, so alerts are only triggered by consecutive out of bounds conditions * add runnables-cli link in runfile * improve frontend first load time through some code splitting * add services count to stack header * fix repo on pull * Add dedicated Deploying state to Deployments and Stacks * move predeploy script before compose config (#584) * Periphery / core version mismatch check / red text * move builders / alerts out of sidebar, into settings * remove force push * list schedules api * dev-1 * actually dev-3 * fix action * filter none procedures * fix schedule api * dev-5 * basic schedules page * prog on schedule page * simplify schedule * use name to sort target * add resource tags to schedule * Schedule page working * dev-6 * remove schedule table type column * reorder schedule table * force confirm dialogs for delete, even if disabled in config * 1.18.1 --------- Co-authored-by: undaunt <31376520+undaunt@users.noreply.github.com> --- Cargo.lock | 26 +- Cargo.toml | 2 +- bin/cli/src/exec.rs | 100 ++-- bin/core/src/api/execute/build.rs | 5 +- bin/core/src/api/execute/mod.rs | 7 +- bin/core/src/api/execute/repo.rs | 2 +- bin/core/src/api/read/mod.rs | 4 + bin/core/src/api/read/schedule.rs | 102 ++++ bin/core/src/api/read/stack.rs | 2 +- bin/core/src/api/write/deployment.rs | 2 +- bin/core/src/helpers/mod.rs | 20 +- bin/core/src/helpers/query.rs | 81 ++- bin/core/src/monitor/alert/server.rs | 267 ++++++---- bin/core/src/monitor/mod.rs | 3 +- bin/core/src/resource/action.rs | 34 +- bin/core/src/resource/build.rs | 16 +- bin/core/src/resource/deployment.rs | 37 +- bin/core/src/resource/procedure.rs | 23 +- bin/core/src/resource/repo.rs | 8 +- bin/core/src/resource/stack.rs | 24 +- bin/core/src/resource/sync.rs | 7 + bin/core/src/sync/deploy.rs | 1 + bin/core/src/sync/user_groups.rs | 2 +- bin/periphery/src/api/git.rs | 17 +- bin/periphery/src/compose.rs | 58 +-- client/core/rs/src/api/execute/mod.rs | 6 +- client/core/rs/src/api/read/mod.rs | 2 + client/core/rs/src/api/read/schedule.rs | 32 ++ client/core/rs/src/entities/action.rs | 18 +- client/core/rs/src/entities/build.rs | 10 +- client/core/rs/src/entities/builder.rs | 3 +- client/core/rs/src/entities/deployment.rs | 27 +- client/core/rs/src/entities/mod.rs | 2 + client/core/rs/src/entities/permission.rs | 2 +- client/core/rs/src/entities/procedure.rs | 2 + client/core/rs/src/entities/repo.rs | 2 + client/core/rs/src/entities/resource.rs | 1 + client/core/rs/src/entities/schedule.rs | 31 ++ client/core/rs/src/entities/stack.rs | 19 +- client/core/rs/src/entities/sync.rs | 2 + client/core/ts/package.json | 2 +- client/core/ts/src/responses.ts | 3 + client/core/ts/src/types.ts | 491 ++++++++++-------- client/periphery/rs/src/api/git.rs | 3 +- frontend/public/client/responses.d.ts | 1 + frontend/public/client/types.d.ts | 482 +++++++++-------- frontend/public/client/types.js | 291 ++++++----- .../src/components/resources/build/index.tsx | 68 +-- .../src/components/resources/build/table.tsx | 22 +- .../components/resources/builder/index.tsx | 135 ++++- frontend/src/components/resources/common.tsx | 46 +- .../deployment/config/components/image.tsx | 1 + .../components/resources/deployment/index.tsx | 32 +- .../components/resources/deployment/table.tsx | 4 +- .../src/components/resources/repo/index.tsx | 80 ++- .../src/components/resources/repo/table.tsx | 15 +- .../resources/resource-sync/index.tsx | 30 +- .../resources/resource-sync/table.tsx | 11 +- .../src/components/resources/server/index.tsx | 50 +- .../src/components/resources/server/table.tsx | 4 +- .../src/components/resources/stack/index.tsx | 92 ++-- .../src/components/resources/stack/table.tsx | 22 +- frontend/src/components/sidebar.tsx | 13 +- frontend/src/components/topbar.tsx | 27 +- frontend/src/components/util.tsx | 24 +- frontend/src/lib/color.ts | 10 +- frontend/src/lib/socket.tsx | 2 + frontend/src/lib/utils.ts | 6 + frontend/src/main.tsx | 27 +- frontend/src/pages/alerts.tsx | 4 +- frontend/src/pages/containers.tsx | 4 +- frontend/src/pages/home/all_resources.tsx | 4 +- frontend/src/pages/home/dashboard.tsx | 4 +- frontend/src/pages/home/index.tsx | 12 +- frontend/src/pages/home/tree.tsx | 8 +- frontend/src/pages/login.tsx | 4 +- frontend/src/pages/resource.tsx | 45 +- frontend/src/pages/resources.tsx | 8 +- frontend/src/pages/schedules.tsx | 146 ++++++ .../src/pages/server-info/container/index.tsx | 4 +- frontend/src/pages/server-info/image.tsx | 4 +- frontend/src/pages/server-info/network.tsx | 4 +- frontend/src/pages/server-info/volume.tsx | 4 +- frontend/src/pages/settings/index.tsx | 19 +- frontend/src/pages/stack-service/index.tsx | 4 +- frontend/src/pages/updates.tsx | 4 +- frontend/src/pages/user-group.tsx | 4 +- frontend/src/pages/user.tsx | 4 +- frontend/src/pages/user_disabled.tsx | 4 +- frontend/src/router.tsx | 134 +++-- lib/git/src/commit.rs | 4 +- lib/git/src/pull.rs | 23 +- runfile.toml | 2 + 93 files changed, 2196 insertions(+), 1264 deletions(-) create mode 100644 bin/core/src/api/read/schedule.rs create mode 100644 client/core/rs/src/api/read/schedule.rs create mode 100644 client/core/rs/src/entities/schedule.rs create mode 100644 frontend/src/pages/schedules.tsx diff --git a/Cargo.lock b/Cargo.lock index 12a3ca94e..4e2f6c0cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -890,7 +890,7 @@ dependencies = [ [[package]] name = "cache" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "tokio", @@ -1057,7 +1057,7 @@ dependencies = [ [[package]] name = "command" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "formatting", @@ -1541,7 +1541,7 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.18.0" +version = "1.18.1" dependencies = [ "thiserror 2.0.12", ] @@ -1621,7 +1621,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.18.0" +version = "1.18.1" dependencies = [ "serror", ] @@ -1783,7 +1783,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "cache", @@ -2520,7 +2520,7 @@ dependencies = [ [[package]] name = "komodo_cli" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "clap", @@ -2536,7 +2536,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "async_timing_util", @@ -2568,7 +2568,7 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "arc-swap", @@ -2637,7 +2637,7 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "async_timing_util", @@ -2681,7 +2681,7 @@ dependencies = [ [[package]] name = "komodo_util" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "dotenvy", @@ -2770,7 +2770,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "komodo_client", @@ -3525,7 +3525,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "komodo_client", @@ -4053,7 +4053,7 @@ dependencies = [ [[package]] name = "response" -version = "1.18.0" +version = "1.18.1" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 286170620..043d5214e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "1.18.0" +version = "1.18.1" edition = "2024" authors = ["mbecker20 "] license = "GPL-3.0-or-later" diff --git a/bin/cli/src/exec.rs b/bin/cli/src/exec.rs index cef7569a5..407f37833 100644 --- a/bin/cli/src/exec.rs +++ b/bin/cli/src/exec.rs @@ -12,7 +12,7 @@ use crate::{ }; pub enum ExecutionResult { - Single(Update), + Single(Box), Batch(BatchExecutionResponse), } @@ -227,7 +227,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::RunAction(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchRunAction(request) => komodo_client() .execute(request) .await @@ -235,7 +235,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::RunProcedure(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchRunProcedure(request) => komodo_client() .execute(request) .await @@ -243,7 +243,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::RunBuild(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchRunBuild(request) => komodo_client() .execute(request) .await @@ -251,11 +251,11 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::CancelBuild(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::Deploy(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchDeploy(request) => komodo_client() .execute(request) .await @@ -263,31 +263,31 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::PullDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StartDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::RestartDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PauseDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::UnpauseDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StopDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DestroyDeployment(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchDestroyDeployment(request) => komodo_client() .execute(request) .await @@ -295,7 +295,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::CloneRepo(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchCloneRepo(request) => komodo_client() .execute(request) .await @@ -303,7 +303,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::PullRepo(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchPullRepo(request) => komodo_client() .execute(request) .await @@ -311,7 +311,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::BuildRepo(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchBuildRepo(request) => komodo_client() .execute(request) .await @@ -319,103 +319,103 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::CancelRepoBuild(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StartContainer(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::RestartContainer(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PauseContainer(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::UnpauseContainer(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StopContainer(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DestroyContainer(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StartAllContainers(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::RestartAllContainers(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PauseAllContainers(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::UnpauseAllContainers(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StopAllContainers(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneContainers(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DeleteNetwork(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneNetworks(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DeleteImage(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneImages(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DeleteVolume(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneVolumes(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneDockerBuilders(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneBuildx(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PruneSystem(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::RunSync(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::CommitSync(request) => komodo_client() .write(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DeployStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchDeployStack(request) => komodo_client() .execute(request) .await @@ -423,7 +423,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::DeployStackIfChanged(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchDeployStackIfChanged(request) => komodo_client() .execute(request) .await @@ -431,7 +431,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::PullStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchPullStack(request) => komodo_client() .execute(request) .await @@ -439,27 +439,27 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::StartStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::RestartStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::PauseStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::UnpauseStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::StopStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::DestroyStack(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::BatchDestroyStack(request) => komodo_client() .execute(request) .await @@ -467,7 +467,7 @@ pub async fn run(execution: Execution) -> anyhow::Result<()> { Execution::TestAlerter(request) => komodo_client() .execute(request) .await - .map(ExecutionResult::Single), + .map(|u| ExecutionResult::Single(u.into())), Execution::Sleep(request) => { let duration = Duration::from_millis(request.duration_ms as u64); diff --git a/bin/core/src/api/execute/build.rs b/bin/core/src/api/execute/build.rs index 6fcb8f1d0..586052f5c 100644 --- a/bin/core/src/api/execute/build.rs +++ b/bin/core/src/api/execute/build.rs @@ -588,8 +588,9 @@ async fn handle_post_build_redeploy(build_id: &str) { redeploy_deployments .into_iter() .map(|deployment| async move { - let state = - get_deployment_state(&deployment).await.unwrap_or_default(); + let state = get_deployment_state(&deployment.id) + .await + .unwrap_or_default(); if state == DeploymentState::Running { let req = super::ExecuteRequest::Deploy(Deploy { deployment: deployment.id.clone(), diff --git a/bin/core/src/api/execute/mod.rs b/bin/core/src/api/execute/mod.rs index d3016591c..82642f032 100644 --- a/bin/core/src/api/execute/mod.rs +++ b/bin/core/src/api/execute/mod.rs @@ -174,8 +174,11 @@ async fn handler( Ok((TypedHeader(ContentType::json()), res)) } +#[typeshare(serialized_as = "Update")] +type BoxUpdate = Box; + pub enum ExecutionResult { - Single(Update), + Single(BoxUpdate), /// The batch contents will be pre serialized here Batch(String), } @@ -245,7 +248,7 @@ pub fn inner_handler( } }); - Ok(ExecutionResult::Single(update)) + Ok(ExecutionResult::Single(update.into())) }) } diff --git a/bin/core/src/api/execute/repo.rs b/bin/core/src/api/execute/repo.rs index b9fa20a4b..24502c8ff 100644 --- a/bin/core/src/api/execute/repo.rs +++ b/bin/core/src/api/execute/repo.rs @@ -163,7 +163,7 @@ impl Resolve for CloneRepo { impl super::BatchExecute for BatchPullRepo { type Resource = Repo; fn single_request(repo: String) -> ExecuteRequest { - ExecuteRequest::CloneRepo(CloneRepo { repo }) + ExecuteRequest::PullRepo(PullRepo { repo }) } } diff --git a/bin/core/src/api/read/mod.rs b/bin/core/src/api/read/mod.rs index 48c8c0392..30e0c6136 100644 --- a/bin/core/src/api/read/mod.rs +++ b/bin/core/src/api/read/mod.rs @@ -43,6 +43,7 @@ mod permission; mod procedure; mod provider; mod repo; +mod schedule; mod server; mod stack; mod sync; @@ -98,6 +99,9 @@ enum ReadRequest { ListActions(ListActions), ListFullActions(ListFullActions), + // ==== SCHEDULE ==== + ListSchedules(ListSchedules), + // ==== SERVER ==== GetServersSummary(GetServersSummary), GetServer(GetServer), diff --git a/bin/core/src/api/read/schedule.rs b/bin/core/src/api/read/schedule.rs new file mode 100644 index 000000000..4b4b9ac1d --- /dev/null +++ b/bin/core/src/api/read/schedule.rs @@ -0,0 +1,102 @@ +use futures::future::join_all; +use komodo_client::{ + api::read::*, + entities::{ + ResourceTarget, action::Action, permission::PermissionLevel, + procedure::Procedure, resource::ResourceQuery, + schedule::Schedule, + }, +}; +use resolver_api::Resolve; + +use crate::{ + helpers::query::{get_all_tags, get_last_run_at}, + resource::list_full_for_user, + schedule::get_schedule_item_info, +}; + +use super::ReadArgs; + +impl Resolve for ListSchedules { + async fn resolve( + self, + args: &ReadArgs, + ) -> serror::Result> { + let all_tags = get_all_tags(None).await?; + let (actions, procedures) = tokio::try_join!( + list_full_for_user::( + ResourceQuery { + names: Default::default(), + tag_behavior: self.tag_behavior, + tags: self.tags.clone(), + specific: Default::default(), + }, + &args.user, + PermissionLevel::Read.into(), + &all_tags, + ), + list_full_for_user::( + ResourceQuery { + names: Default::default(), + tag_behavior: self.tag_behavior, + tags: self.tags.clone(), + specific: Default::default(), + }, + &args.user, + PermissionLevel::Read.into(), + &all_tags, + ) + )?; + let actions = actions.into_iter().map(async |action| { + let (next_scheduled_run, schedule_error) = + get_schedule_item_info(&ResourceTarget::Action( + action.id.clone(), + )); + let last_run_at = + get_last_run_at::(&action.id).await.unwrap_or(None); + Schedule { + target: ResourceTarget::Action(action.id), + name: action.name, + enabled: action.config.schedule_enabled, + schedule_format: action.config.schedule_format, + schedule: action.config.schedule, + schedule_timezone: action.config.schedule_timezone, + tags: action.tags, + last_run_at, + next_scheduled_run, + schedule_error, + } + }); + let procedures = procedures.into_iter().map(async |procedure| { + let (next_scheduled_run, schedule_error) = + get_schedule_item_info(&ResourceTarget::Procedure( + procedure.id.clone(), + )); + let last_run_at = get_last_run_at::(&procedure.id) + .await + .unwrap_or(None); + Schedule { + target: ResourceTarget::Procedure(procedure.id), + name: procedure.name, + enabled: procedure.config.schedule_enabled, + schedule_format: procedure.config.schedule_format, + schedule: procedure.config.schedule, + schedule_timezone: procedure.config.schedule_timezone, + tags: procedure.tags, + last_run_at, + next_scheduled_run, + schedule_error, + } + }); + let (actions, procedures) = + tokio::join!(join_all(actions), join_all(procedures)); + + Ok( + actions + .into_iter() + .chain(procedures) + .filter(|s| !s.schedule.is_empty()) + .collect(), + ) + } +} diff --git a/bin/core/src/api/read/stack.rs b/bin/core/src/api/read/stack.rs index 008e64224..5dbd88e38 100644 --- a/bin/core/src/api/read/stack.rs +++ b/bin/core/src/api/read/stack.rs @@ -176,7 +176,7 @@ impl Resolve for InspectStackContainer { .curr .services; let Some(name) = services - .into_iter() + .iter() .find(|s| s.service == service) .and_then(|s| s.container.as_ref().map(|c| c.name.clone())) else { diff --git a/bin/core/src/api/write/deployment.rs b/bin/core/src/api/write/deployment.rs index 55938d952..46e9193ce 100644 --- a/bin/core/src/api/write/deployment.rs +++ b/bin/core/src/api/write/deployment.rs @@ -209,7 +209,7 @@ impl Resolve for RenameDeployment { let name = to_docker_compatible_name(&self.name); - let container_state = get_deployment_state(&deployment).await?; + let container_state = get_deployment_state(&deployment.id).await?; if container_state == DeploymentState::Unknown { return Err( diff --git a/bin/core/src/helpers/mod.rs b/bin/core/src/helpers/mod.rs index 47a72abce..1e83aa086 100644 --- a/bin/core/src/helpers/mod.rs +++ b/bin/core/src/helpers/mod.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{fmt::Write, time::Duration}; use anyhow::{Context, anyhow}; use indexmap::IndexSet; @@ -194,3 +194,21 @@ pub fn flatten_document(doc: Document) -> Document { target } + +pub fn repo_link( + provider: &str, + repo: &str, + branch: &str, + https: bool, +) -> String { + let mut res = format!( + "http{}://{provider}/{repo}", + if https { "s" } else { "" } + ); + // Each provider uses a different link format to get to branches. + // At least can support github for branch aware link. + if provider == "github.com" { + let _ = write!(&mut res, "/tree/{branch}"); + } + res +} diff --git a/bin/core/src/helpers/query.rs b/bin/core/src/helpers/query.rs index 0c7a387b7..78263b556 100644 --- a/bin/core/src/helpers/query.rs +++ b/bin/core/src/helpers/query.rs @@ -8,14 +8,14 @@ use anyhow::{Context, anyhow}; use async_timing_util::{ONE_MIN_MS, unix_timestamp_ms}; use komodo_client::entities::{ Operation, ResourceTarget, ResourceTargetVariant, - action::Action, + action::{Action, ActionState}, alerter::Alerter, build::Build, builder::Builder, deployment::{Deployment, DeploymentState}, docker::container::{ContainerListItem, ContainerStateStatusEnum}, permission::{PermissionLevel, PermissionLevelAndSpecifics}, - procedure::Procedure, + procedure::{Procedure, ProcedureState}, repo::Repo, server::{Server, ServerState}, stack::{Stack, StackServiceNames, StackState}, @@ -40,9 +40,13 @@ use tokio::sync::Mutex; use crate::{ config::core_config, permission::get_user_permission_on_resource, - resource, + resource::{self, KomodoResource}, stack::compose_container_match_regex, - state::{db_client, deployment_status_cache, stack_status_cache}, + state::{ + action_state_cache, action_states, db_client, + deployment_status_cache, procedure_state_cache, + stack_status_cache, + }, }; use super::periphery_client; @@ -88,10 +92,22 @@ pub async fn get_server_state(server: &Server) -> ServerState { #[instrument(level = "debug")] pub async fn get_deployment_state( - deployment: &Deployment, + id: &String, ) -> anyhow::Result { + if action_states() + .deployment + .get(id) + .await + .map(|s| s.get().map(|s| s.deploying)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + return Ok(DeploymentState::Deploying); + } let state = deployment_status_cache() - .get(&deployment.id) + .get(id) .await .unwrap_or_default() .curr @@ -424,3 +440,56 @@ pub async fn get_system_info( }; Ok(res) } + +/// Get last time procedure / action was run using Update query. +/// Ignored whether run was successful. +pub async fn get_last_run_at( + id: &String, +) -> anyhow::Result> { + let resource_type = R::resource_type(); + let res = db_client() + .updates + .find_one(doc! { + "target.type": resource_type.as_ref(), + "target.id": id, + "operation": format!("Run{resource_type}"), + "status": "Complete" + }) + .sort(doc! { "start_ts": -1 }) + .await + .context("Failed to query updates collection for last run time")? + .map(|u| u.start_ts); + Ok(res) +} + +pub async fn get_action_state(id: &String) -> ActionState { + if action_states() + .action + .get(id) + .await + .map(|s| s.get().map(|s| s.running)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + return ActionState::Running; + } + action_state_cache().get(id).await.unwrap_or_default() +} + +pub async fn get_procedure_state(id: &String) -> ProcedureState { + if action_states() + .procedure + .get(id) + .await + .map(|s| s.get().map(|s| s.running)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + return ProcedureState::Running; + } + procedure_state_cache().get(id).await.unwrap_or_default() +} diff --git a/bin/core/src/monitor/alert/server.rs b/bin/core/src/monitor/alert/server.rs index 2e9305ae6..f0c15f0c9 100644 --- a/bin/core/src/monitor/alert/server.rs +++ b/bin/core/src/monitor/alert/server.rs @@ -1,4 +1,9 @@ -use std::{collections::HashMap, path::PathBuf, str::FromStr}; +use std::{ + collections::HashMap, + path::PathBuf, + str::FromStr, + sync::{Mutex, OnceLock}, +}; use anyhow::Context; use derive_variants::ExtractVariant; @@ -32,7 +37,8 @@ pub async fn alert_servers( ) { let server_statuses = server_status_cache().get_list().await; - let (alerts, disk_alerts) = match get_open_alerts().await { + let (open_alerts, open_disk_alerts) = match get_open_alerts().await + { Ok(alerts) => alerts, Err(e) => { error!("{e:#}"); @@ -44,12 +50,14 @@ pub async fn alert_servers( let mut alerts_to_update = Vec::<(Alert, SendAlerts)>::new(); let mut alert_ids_to_close = Vec::<(Alert, SendAlerts)>::new(); + let buffer = alert_buffer(); + for server_status in server_statuses { let Some(server) = servers.remove(&server_status.id) else { continue; }; - let server_alerts = - alerts.get(&ResourceTarget::Server(server_status.id.clone())); + let server_alerts = open_alerts + .get(&ResourceTarget::Server(server_status.id.clone())); // =================== // SERVER HEALTH @@ -59,23 +67,28 @@ pub async fn alert_servers( }); match (server_status.state, health_alert) { (ServerState::NotOk, None) => { - // open unreachable alert - let alert = Alert { - id: Default::default(), - ts, - resolved: false, - resolved_ts: None, - level: SeverityLevel::Critical, - target: ResourceTarget::Server(server_status.id.clone()), - data: AlertData::ServerUnreachable { - id: server_status.id.clone(), - name: server.name.clone(), - region: optional_string(&server.config.region), - err: server_status.err.clone(), - }, - }; - alerts_to_open - .push((alert, server.config.send_unreachable_alerts)) + if buffer.ready_to_open( + server_status.id.clone(), + AlertDataVariant::ServerUnreachable, + ) { + // open unreachable alert + let alert = Alert { + id: Default::default(), + ts, + resolved: false, + resolved_ts: None, + level: SeverityLevel::Critical, + target: ResourceTarget::Server(server_status.id.clone()), + data: AlertData::ServerUnreachable { + id: server_status.id.clone(), + name: server.name.clone(), + region: optional_string(&server.config.region), + err: server_status.err.clone(), + }, + }; + alerts_to_open + .push((alert, server.config.send_unreachable_alerts)) + } } (ServerState::NotOk, Some(alert)) => { // update alert err @@ -109,7 +122,11 @@ pub async fn alert_servers( server.config.send_unreachable_alerts, )); } - _ => {} + (ServerState::Ok | ServerState::Disabled, None) => buffer + .reset( + server_status.id.clone(), + AlertDataVariant::ServerUnreachable, + ), } let Some(health) = &server_status.health else { @@ -127,25 +144,30 @@ pub async fn alert_servers( { (SeverityLevel::Warning | SeverityLevel::Critical, None, _) => { // open alert - let alert = Alert { - id: Default::default(), - ts, - resolved: false, - resolved_ts: None, - 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.config.region), - percentage: server_status - .stats - .as_ref() - .map(|s| s.cpu_perc as f64) - .unwrap_or(0.0), - }, - }; - alerts_to_open.push((alert, server.config.send_cpu_alerts)); + if buffer.ready_to_open( + server_status.id.clone(), + AlertDataVariant::ServerCpu, + ) { + let alert = Alert { + id: Default::default(), + ts, + resolved: false, + resolved_ts: None, + 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.config.region), + percentage: server_status + .stats + .as_ref() + .map(|s| s.cpu_perc as f64) + .unwrap_or(0.0), + }, + }; + alerts_to_open.push((alert, server.config.send_cpu_alerts)); + } } ( SeverityLevel::Warning | SeverityLevel::Critical, @@ -184,7 +206,9 @@ pub async fn alert_servers( alert_ids_to_close .push((alert, server.config.send_cpu_alerts)) } - _ => {} + (SeverityLevel::Ok, Some(_), false) => {} + (SeverityLevel::Ok, None, _) => buffer + .reset(server_status.id.clone(), AlertDataVariant::ServerCpu), } // =================== @@ -198,30 +222,35 @@ pub async fn alert_servers( { (SeverityLevel::Warning | SeverityLevel::Critical, None, _) => { // open alert - let alert = Alert { - id: Default::default(), - ts, - resolved: false, - resolved_ts: None, - 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.config.region), - total_gb: server_status - .stats - .as_ref() - .map(|s| s.mem_total_gb) - .unwrap_or(0.0), - used_gb: server_status - .stats - .as_ref() - .map(|s| s.mem_used_gb) - .unwrap_or(0.0), - }, - }; - alerts_to_open.push((alert, server.config.send_mem_alerts)); + if buffer.ready_to_open( + server_status.id.clone(), + AlertDataVariant::ServerMem, + ) { + let alert = Alert { + id: Default::default(), + ts, + resolved: false, + resolved_ts: None, + 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.config.region), + total_gb: server_status + .stats + .as_ref() + .map(|s| s.mem_total_gb) + .unwrap_or(0.0), + used_gb: server_status + .stats + .as_ref() + .map(|s| s.mem_used_gb) + .unwrap_or(0.0), + }, + }; + alerts_to_open.push((alert, server.config.send_mem_alerts)); + } } ( SeverityLevel::Warning | SeverityLevel::Critical, @@ -270,14 +299,16 @@ pub async fn alert_servers( alert_ids_to_close .push((alert, server.config.send_mem_alerts)) } - _ => {} + (SeverityLevel::Ok, Some(_), false) => {} + (SeverityLevel::Ok, None, _) => buffer + .reset(server_status.id.clone(), AlertDataVariant::ServerMem), } // =================== // SERVER DISK // =================== - let server_disk_alerts = disk_alerts + let server_disk_alerts = open_disk_alerts .get(&ResourceTarget::Server(server_status.id.clone())); for (path, health) in &health.disks { @@ -291,27 +322,38 @@ pub async fn alert_servers( None, _, ) => { - let disk = server_status.stats.as_ref().and_then(|stats| { - stats.disks.iter().find(|disk| disk.mount == *path) - }); - let alert = Alert { - id: Default::default(), - ts, - resolved: false, - resolved_ts: None, - 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.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.config.send_disk_alerts)); + // open alert + if buffer.ready_to_open( + server_status.id.clone(), + AlertDataVariant::ServerDisk, + ) { + let disk = + server_status.stats.as_ref().and_then(|stats| { + stats.disks.iter().find(|disk| disk.mount == *path) + }); + let alert = Alert { + id: Default::default(), + ts, + resolved: false, + resolved_ts: None, + 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.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.config.send_disk_alerts)); + } } ( SeverityLevel::Warning | SeverityLevel::Critical, @@ -354,7 +396,11 @@ pub async fn alert_servers( alert_ids_to_close .push((alert, server.config.send_disk_alerts)) } - _ => {} + (SeverityLevel::Ok, Some(_), false) => {} + (SeverityLevel::Ok, None, _) => buffer.reset( + server_status.id.clone(), + AlertDataVariant::ServerDisk, + ), } } @@ -372,14 +418,14 @@ pub async fn alert_servers( } tokio::join!( - open_alerts(&alerts_to_open), + open_new_alerts(&alerts_to_open), update_alerts(&alerts_to_update), resolve_alerts(&alert_ids_to_close), ); } #[instrument(level = "debug")] -async fn open_alerts(alerts: &[(Alert, SendAlerts)]) { +async fn open_new_alerts(alerts: &[(Alert, SendAlerts)]) { if alerts.is_empty() { return; } @@ -560,3 +606,40 @@ async fn get_open_alerts() Ok((map, disk_map)) } + +/// Alerts should only be opened after +/// 2 *consecutive* alerting conditions. +/// This reduces alerting noise. +#[derive(Default)] +struct AlertBuffer { + /// (ServerId, AlertType) -> should_open. + buffer: Mutex>, +} + +impl AlertBuffer { + fn reset(&self, server_id: String, variant: AlertDataVariant) { + let mut lock = self.buffer.lock().unwrap(); + lock.remove(&(server_id, variant)); + } + + fn ready_to_open( + &self, + server_id: String, + variant: AlertDataVariant, + ) -> bool { + let mut lock = self.buffer.lock().unwrap(); + let ready = lock.entry((server_id, variant)).or_default(); + if *ready { + *ready = false; + true + } else { + *ready = true; + false + } + } +} + +fn alert_buffer() -> &'static AlertBuffer { + static ALERT_BUFFER: OnceLock = OnceLock::new(); + ALERT_BUFFER.get_or_init(Default::default) +} diff --git a/bin/core/src/monitor/mod.rs b/bin/core/src/monitor/mod.rs index 77c1966b4..bcaadf722 100644 --- a/bin/core/src/monitor/mod.rs +++ b/bin/core/src/monitor/mod.rs @@ -267,8 +267,9 @@ pub async fn update_cache_for_server(server: &Server) { path: optional_string(&repo.config.path), }) .await - .map(|r| (r.hash, r.message)) .ok() + .flatten() + .map(|c| (c.hash, c.message)) .unzip(); status_cache .insert( diff --git a/bin/core/src/resource/action.rs b/bin/core/src/resource/action.rs index fad566754..58a4de05c 100644 --- a/bin/core/src/resource/action.rs +++ b/bin/core/src/resource/action.rs @@ -2,11 +2,11 @@ use std::time::Duration; use anyhow::Context; use komodo_client::entities::{ - Operation, ResourceTarget, ResourceTargetVariant, + NoData, Operation, ResourceTarget, ResourceTargetVariant, action::{ - Action, ActionConfig, ActionConfigDiff, ActionInfo, - ActionListItem, ActionListItemInfo, ActionQuerySpecifics, - ActionState, PartialActionConfig, + Action, ActionConfig, ActionConfigDiff, ActionListItem, + ActionListItemInfo, ActionQuerySpecifics, ActionState, + PartialActionConfig, }, resource::Resource, update::Update, @@ -18,6 +18,7 @@ use mungos::{ }; use crate::{ + helpers::query::{get_action_state, get_last_run_at}, schedule::{ cancel_schedule, get_schedule_item_info, update_schedule, }, @@ -28,7 +29,7 @@ impl super::KomodoResource for Action { type Config = ActionConfig; type PartialConfig = PartialActionConfig; type ConfigDiff = ActionConfigDiff; - type Info = ActionInfo; + type Info = NoData; type ListItem = ActionListItem; type QuerySpecifics = ActionQuerySpecifics; @@ -48,7 +49,10 @@ impl super::KomodoResource for Action { async fn to_list_item( action: Resource, ) -> Self::ListItem { - let state = get_action_state(&action.id).await; + let (state, last_run_at) = tokio::join!( + get_action_state(&action.id), + get_last_run_at::(&action.id) + ); let (next_scheduled_run, schedule_error) = get_schedule_item_info( &ResourceTarget::Action(action.id.clone()), ); @@ -59,7 +63,7 @@ impl super::KomodoResource for Action { resource_type: ResourceTargetVariant::Action, info: ActionListItemInfo { state, - last_run_at: action.info.last_run_at, + last_run_at: last_run_at.unwrap_or(None), next_scheduled_run, schedule_error, }, @@ -181,22 +185,6 @@ pub async fn refresh_action_state_cache() { }); } -async fn get_action_state(id: &String) -> ActionState { - if action_states() - .action - .get(id) - .await - .map(|s| s.get().map(|s| s.running)) - .transpose() - .ok() - .flatten() - .unwrap_or_default() - { - return ActionState::Running; - } - action_state_cache().get(id).await.unwrap_or_default() -} - async fn get_action_state_from_db(id: &str) -> ActionState { async { let state = db_client() diff --git a/bin/core/src/resource/build.rs b/bin/core/src/resource/build.rs index 53357f112..79b823106 100644 --- a/bin/core/src/resource/build.rs +++ b/bin/core/src/resource/build.rs @@ -29,7 +29,9 @@ use resolver_api::Resolve; use crate::{ api::write::WriteArgs, config::core_config, - helpers::{empty_or_only_spaces, query::get_latest_update}, + helpers::{ + empty_or_only_spaces, query::get_latest_update, repo_link, + }, state::{action_states, build_state_cache, db_client}, }; @@ -72,9 +74,15 @@ impl super::KomodoResource for Build { version: build.config.version, builder_id: build.config.builder_id, files_on_host: build.config.files_on_host, - git_provider: optional_string(build.config.git_provider), - repo: optional_string(build.config.repo), - branch: optional_string(build.config.branch), + repo_link: repo_link( + &build.config.git_provider, + &build.config.repo, + &build.config.branch, + build.config.git_https, + ), + git_provider: build.config.git_provider, + repo: build.config.repo, + branch: build.config.branch, image_registry_domain: optional_string( build.config.image_registry.domain, ), diff --git a/bin/core/src/resource/deployment.rs b/bin/core/src/resource/deployment.rs index 12e69e03b..5d76e069b 100644 --- a/bin/core/src/resource/deployment.rs +++ b/bin/core/src/resource/deployment.rs @@ -78,6 +78,20 @@ impl super::KomodoResource for Deployment { deployment: Resource, ) -> Self::ListItem { let status = deployment_status_cache().get(&deployment.id).await; + let state = if action_states() + .deployment + .get(&deployment.id) + .await + .map(|s| s.get().map(|s| s.deploying)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + DeploymentState::Deploying + } else { + status.as_ref().map(|s| s.curr.state).unwrap_or_default() + }; let (build_image, build_id) = match deployment.config.image { DeploymentImage::Build { build_id, version } => { let (build_name, build_id, build_version) = @@ -117,10 +131,7 @@ impl super::KomodoResource for Deployment { tags: deployment.tags, resource_type: ResourceTargetVariant::Deployment, info: DeploymentListItemInfo { - state: status - .as_ref() - .map(|s| s.curr.state) - .unwrap_or_default(), + state, status: status.as_ref().and_then(|s| { s.curr.container.as_ref().and_then(|c| c.status.to_owned()) }), @@ -217,9 +228,9 @@ impl super::KomodoResource for Deployment { deployment: &Resource, update: &mut Update, ) -> anyhow::Result<()> { - let state = get_deployment_state(deployment) + let state = get_deployment_state(&deployment.id) .await - .context("failed to get container state")?; + .context("Failed to get deployment state")?; if matches!( state, DeploymentState::NotDeployed | DeploymentState::Unknown @@ -235,7 +246,7 @@ impl super::KomodoResource for Deployment { Ok(server) => server, Err(e) => { update.push_error_log( - "remove container", + "Remove Container", format_serror( &e.context(format!( "failed to retrieve server at {} from db.", @@ -250,8 +261,8 @@ impl super::KomodoResource for Deployment { if !server.config.enabled { // Don't need to update.push_simple_log( - "remove container", - "skipping container removal, server is disabled.", + "Remove Container", + "Skipping container removal, server is disabled.", ); return Ok(()); } @@ -261,9 +272,9 @@ impl super::KomodoResource for Deployment { // This case won't ever happen, as periphery_client only fallible if the server is disabled. // Leaving it for completeness sake update.push_error_log( - "remove container", + "Remove Container", format_serror( - &e.context("failed to get periphery client").into(), + &e.context("Failed to get periphery client").into(), ), ); return Ok(()); @@ -279,9 +290,9 @@ impl super::KomodoResource for Deployment { { Ok(log) => update.logs.push(log), Err(e) => update.push_error_log( - "remove container", + "Remove Container", format_serror( - &e.context("failed to remove container").into(), + &e.context("Failed to remove container").into(), ), ), }; diff --git a/bin/core/src/resource/procedure.rs b/bin/core/src/resource/procedure.rs index ed605fd1c..5b51c7d95 100644 --- a/bin/core/src/resource/procedure.rs +++ b/bin/core/src/resource/procedure.rs @@ -31,6 +31,7 @@ use mungos::{ use crate::{ config::core_config, + helpers::query::{get_last_run_at, get_procedure_state}, schedule::{ cancel_schedule, get_schedule_item_info, update_schedule, }, @@ -61,7 +62,10 @@ impl super::KomodoResource for Procedure { async fn to_list_item( procedure: Resource, ) -> Self::ListItem { - let state = get_procedure_state(&procedure.id).await; + let (state, last_run_at) = tokio::join!( + get_procedure_state(&procedure.id), + get_last_run_at::(&procedure.id) + ); let (next_scheduled_run, schedule_error) = get_schedule_item_info( &ResourceTarget::Procedure(procedure.id.clone()), ); @@ -73,6 +77,7 @@ impl super::KomodoResource for Procedure { info: ProcedureListItemInfo { stages: procedure.config.stages.len() as i64, state, + last_run_at: last_run_at.unwrap_or(None), next_scheduled_run, schedule_error, }, @@ -754,22 +759,6 @@ pub async fn refresh_procedure_state_cache() { }); } -async fn get_procedure_state(id: &String) -> ProcedureState { - if action_states() - .procedure - .get(id) - .await - .map(|s| s.get().map(|s| s.running)) - .transpose() - .ok() - .flatten() - .unwrap_or_default() - { - return ProcedureState::Running; - } - procedure_state_cache().get(id).await.unwrap_or_default() -} - async fn get_procedure_state_from_db(id: &str) -> ProcedureState { async { let state = db_client() diff --git a/bin/core/src/resource/repo.rs b/bin/core/src/resource/repo.rs index 44dfbdb8e..ad23a452a 100644 --- a/bin/core/src/resource/repo.rs +++ b/bin/core/src/resource/repo.rs @@ -24,7 +24,7 @@ use periphery_client::api::git::DeleteRepo; use crate::{ config::core_config, - helpers::periphery_client, + helpers::{periphery_client, repo_link}, state::{ action_states, db_client, repo_state_cache, repo_status_cache, }, @@ -73,6 +73,12 @@ impl super::KomodoResource for Repo { builder_id: repo.config.builder_id, last_pulled_at: repo.info.last_pulled_at, last_built_at: repo.info.last_built_at, + repo_link: repo_link( + &repo.config.git_provider, + &repo.config.repo, + &repo.config.branch, + repo.config.git_https, + ), git_provider: repo.config.git_provider, repo: repo.config.repo, branch: repo.config.branch, diff --git a/bin/core/src/resource/stack.rs b/bin/core/src/resource/stack.rs index 8b964f364..ffd63506e 100644 --- a/bin/core/src/resource/stack.rs +++ b/bin/core/src/resource/stack.rs @@ -25,7 +25,7 @@ use resolver_api::Resolve; use crate::{ api::write::WriteArgs, config::core_config, - helpers::{periphery_client, query::get_stack_state}, + helpers::{periphery_client, query::get_stack_state, repo_link}, monitor::update_cache_for_server, state::{ action_states, db_client, server_status_cache, stack_status_cache, @@ -79,8 +79,20 @@ impl super::KomodoResource for Stack { stack: Resource, ) -> Self::ListItem { let status = stack_status_cache().get(&stack.id).await; - let state = - status.as_ref().map(|s| s.curr.state).unwrap_or_default(); + let state = if action_states() + .stack + .get(&stack.id) + .await + .map(|s| s.get().map(|s| s.deploying)) + .transpose() + .ok() + .flatten() + .unwrap_or_default() + { + StackState::Deploying + } else { + status.as_ref().map(|s| s.curr.state).unwrap_or_default() + }; let project_name = stack.project_name(false); let services = status .as_ref() @@ -139,6 +151,12 @@ impl super::KomodoResource for Stack { server_id: stack.config.server_id, missing_files: stack.info.missing_files, files_on_host: stack.config.files_on_host, + repo_link: repo_link( + &stack.config.git_provider, + &stack.config.repo, + &stack.config.branch, + stack.config.git_https, + ), git_provider: stack.config.git_provider, repo: stack.config.repo, branch: stack.config.branch, diff --git a/bin/core/src/resource/sync.rs b/bin/core/src/resource/sync.rs index 9da38adf8..492629c4f 100644 --- a/bin/core/src/resource/sync.rs +++ b/bin/core/src/resource/sync.rs @@ -22,6 +22,7 @@ use resolver_api::Resolve; use crate::{ api::write::WriteArgs, + helpers::repo_link, state::{action_states, db_client}, }; @@ -61,6 +62,12 @@ impl super::KomodoResource for ResourceSync { file_contents: !resource_sync.config.file_contents.is_empty(), files_on_host: resource_sync.config.files_on_host, managed: resource_sync.config.managed, + repo_link: repo_link( + &resource_sync.config.git_provider, + &resource_sync.config.repo, + &resource_sync.config.branch, + resource_sync.config.git_https, + ), git_provider: resource_sync.config.git_provider, repo: resource_sync.config.repo, branch: resource_sync.config.branch, diff --git a/bin/core/src/sync/deploy.rs b/bin/core/src/sync/deploy.rs index 32e4587bb..d86f36457 100644 --- a/bin/core/src/sync/deploy.rs +++ b/bin/core/src/sync/deploy.rs @@ -555,6 +555,7 @@ fn build_cache_for_stack<'a>( // Here can diff the changes, to see if they merit a redeploy. // See if any remote contents don't match deployed contents + #[allow(clippy::single_match)] match ( &original.info.deployed_contents, &original.info.remote_contents, diff --git a/bin/core/src/sync/user_groups.rs b/bin/core/src/sync/user_groups.rs index 2d5bffef2..76b23710b 100644 --- a/bin/core/src/sync/user_groups.rs +++ b/bin/core/src/sync/user_groups.rs @@ -799,7 +799,7 @@ async fn expand_user_group_permissions( if id.is_empty() { continue; } - let matcher = Matcher::new(&id)?; + let matcher = Matcher::new(id)?; match variant { ResourceTargetVariant::Build => { let permissions = all_resources diff --git a/bin/periphery/src/api/git.rs b/bin/periphery/src/api/git.rs index 8869fd58a..aa9bd5611 100644 --- a/bin/periphery/src/api/git.rs +++ b/bin/periphery/src/api/git.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, anyhow}; +use anyhow::Context; use formatting::format_serror; use git::GitRes; use komodo_client::entities::{CloneArgs, LatestCommit, update::Log}; @@ -17,21 +17,16 @@ impl Resolve for GetLatestCommit { async fn resolve( self, _: &super::Args, - ) -> serror::Result { + ) -> serror::Result> { let repo_path = match self.path { Some(p) => PathBuf::from(p), None => periphery_config().repo_dir().join(self.name), }; - if !repo_path.is_dir() { - return Err( - anyhow!( - "Repo path {} is not directory. is it cloned?", - repo_path.display() - ) - .into(), - ); + // Make sure its a repo, or return null to avoid log spam + if !repo_path.is_dir() || !repo_path.join(".git").is_dir() { + return Ok(None); } - Ok(git::get_commit_hash_info(&repo_path).await?) + Ok(Some(git::get_commit_hash_info(&repo_path).await?)) } } diff --git a/bin/periphery/src/compose.rs b/bin/periphery/src/compose.rs index 833aba139..a0dec0de1 100644 --- a/bin/periphery/src/compose.rs +++ b/bin/periphery/src/compose.rs @@ -172,6 +172,35 @@ pub async fn compose_up( output }); + // Pre deploy command + let pre_deploy_path = + run_directory.join(&stack.config.pre_deploy.path); + if let Some(log) = if stack.config.skip_secret_interp { + run_komodo_command_multiline( + "Pre Deploy", + pre_deploy_path.as_ref(), + &stack.config.pre_deploy.command, + ) + .await + } else { + run_komodo_command_with_interpolation( + "Pre Deploy", + pre_deploy_path.as_ref(), + &stack.config.pre_deploy.command, + true, + &periphery_config().secrets, + &replacers, + ) + .await + } { + res.logs.push(log); + } + if !all_logs_success(&res.logs) { + return Err(anyhow!( + "Failed at running pre_deploy command, stopping the run." + )); + } + // Uses 'docker compose config' command to extract services (including image) // after performing interpolation { @@ -291,35 +320,6 @@ pub async fn compose_up( } } - // Pre deploy command - let pre_deploy_path = - run_directory.join(&stack.config.pre_deploy.path); - if let Some(log) = if stack.config.skip_secret_interp { - run_komodo_command_multiline( - "Pre Deploy", - pre_deploy_path.as_ref(), - &stack.config.pre_deploy.command, - ) - .await - } else { - run_komodo_command_with_interpolation( - "Pre Deploy", - pre_deploy_path.as_ref(), - &stack.config.pre_deploy.command, - true, - &periphery_config().secrets, - &replacers, - ) - .await - } { - res.logs.push(log); - } - if !all_logs_success(&res.logs) { - return Err(anyhow!( - "Failed at running pre_deploy command, stopping the run." - )); - } - if stack.config.destroy_before_deploy // Also check if project name changed, which also requires taking down. || last_project_name != project_name diff --git a/client/core/rs/src/api/execute/mod.rs b/client/core/rs/src/api/execute/mod.rs index 5f87eaa0b..e3e0ee0fa 100644 --- a/client/core/rs/src/api/execute/mod.rs +++ b/client/core/rs/src/api/execute/mod.rs @@ -160,14 +160,14 @@ pub enum BatchExecutionResponseItem { Err(BatchExecutionResponseItemErr), } -impl From> +impl From, BatchExecutionResponseItemErr>> for BatchExecutionResponseItem { fn from( - value: Result, + value: Result, BatchExecutionResponseItemErr>, ) -> Self { match value { - Ok(update) => Self::Ok(update), + Ok(update) => Self::Ok(*update), Err(e) => Self::Err(e), } } diff --git a/client/core/rs/src/api/read/mod.rs b/client/core/rs/src/api/read/mod.rs index d007eec40..d04ff73a4 100644 --- a/client/core/rs/src/api/read/mod.rs +++ b/client/core/rs/src/api/read/mod.rs @@ -13,6 +13,7 @@ mod permission; mod procedure; mod provider; mod repo; +mod schedule; mod server; mod stack; mod sync; @@ -33,6 +34,7 @@ pub use permission::*; pub use procedure::*; pub use provider::*; pub use repo::*; +pub use schedule::*; pub use server::*; pub use stack::*; pub use sync::*; diff --git a/client/core/rs/src/api/read/schedule.rs b/client/core/rs/src/api/read/schedule.rs new file mode 100644 index 000000000..d9b115c42 --- /dev/null +++ b/client/core/rs/src/api/read/schedule.rs @@ -0,0 +1,32 @@ +use derive_empty_traits::EmptyTraits; +use resolver_api::Resolve; +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::{ + deserializers::string_list_deserializer, + entities::{resource::TagBehavior, schedule::Schedule}, +}; + +use super::KomodoReadRequest; + +/// List configured schedules. +/// Response: [ListSchedulesResponse]. +#[typeshare] +#[derive( + Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits, +)] +#[empty_traits(KomodoReadRequest)] +#[response(ListSchedulesResponse)] +#[error(serror::Error)] +pub struct ListSchedules { + /// Pass Vec of tag ids or tag names + #[serde(default, deserialize_with = "string_list_deserializer")] + pub tags: Vec, + /// 'All' or 'Any' + #[serde(default)] + pub tag_behavior: TagBehavior, +} + +#[typeshare] +pub type ListSchedulesResponse = Vec; diff --git a/client/core/rs/src/entities/action.rs b/client/core/rs/src/entities/action.rs index ef8e14e1d..8ff04988e 100644 --- a/client/core/rs/src/entities/action.rs +++ b/client/core/rs/src/entities/action.rs @@ -10,7 +10,7 @@ use crate::{ deserializers::{ file_contents_deserializer, option_file_contents_deserializer, }, - entities::I64, + entities::{I64, NoData}, }; use super::{ @@ -24,11 +24,11 @@ pub type ActionListItem = ResourceListItem; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct ActionListItemInfo { - /// Action last run timestamp in ms. - pub last_run_at: I64, /// Whether last action run successful pub state: ActionState, - /// If the procedure has schedule enabled, this is the + /// Action last successful run timestamp in ms. + pub last_run_at: Option, + /// If the action has schedule enabled, this is the /// next scheduled run time in unix ms. pub next_scheduled_run: Option, /// If there is an error parsing schedule expression, @@ -53,15 +53,7 @@ pub enum ActionState { } #[typeshare] -pub type Action = Resource; - -#[typeshare] -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct ActionInfo { - /// When action was last run - #[serde(default)] - pub last_run_at: I64, -} +pub type Action = Resource; #[typeshare(serialized_as = "Partial")] pub type _PartialActionConfig = PartialActionConfig; diff --git a/client/core/rs/src/entities/build.rs b/client/core/rs/src/entities/build.rs index 764bdc25e..8bf4604e1 100644 --- a/client/core/rs/src/entities/build.rs +++ b/client/core/rs/src/entities/build.rs @@ -45,11 +45,15 @@ pub struct BuildListItemInfo { pub files_on_host: bool, /// The git provider domain - pub git_provider: Option, + pub git_provider: String, /// The repo used as the source of the build - pub repo: Option, + pub repo: String, /// The branch of the repo - pub branch: Option, + pub branch: String, + /// Full link to the repo. + pub repo_link: String, + + /// Latest built short commit hash, or null. pub built_hash: Option, /// Latest short commit hash, or null. Only for repo based stacks diff --git a/client/core/rs/src/entities/builder.rs b/client/core/rs/src/entities/builder.rs index 3597416b2..82ed586b2 100644 --- a/client/core/rs/src/entities/builder.rs +++ b/client/core/rs/src/entities/builder.rs @@ -27,8 +27,9 @@ pub type _PartialBuilderConfig = PartialBuilderConfig; #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BuilderListItemInfo { - /// 'Server' or 'Aws' + /// 'Url', 'Server', or 'Aws' pub builder_type: String, + /// If 'Url': null /// If 'Server': the server id /// If 'Aws': the instance type (eg. c5.xlarge) pub instance_type: Option, diff --git a/client/core/rs/src/entities/deployment.rs b/client/core/rs/src/entities/deployment.rs index 8c3fb5bc1..0f31fe18f 100644 --- a/client/core/rs/src/entities/deployment.rs +++ b/client/core/rs/src/entities/deployment.rs @@ -338,16 +338,27 @@ pub fn conversions_from_str( #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum DeploymentState { + /// The deployment is currently re/deploying + Deploying, + /// Container is running + Running, + /// Container is created but not running + Created, + /// Container is in restart loop + Restarting, + /// Container is being removed + Removing, + /// Container is paused + Paused, + /// Container is exited + Exited, + /// Container is dead + Dead, + /// The deployment is not deployed (no matching container) + NotDeployed, + /// Server not reachable for status #[default] Unknown, - NotDeployed, - Created, - Restarting, - Running, - Removing, - Paused, - Exited, - Dead, } impl From for DeploymentState { diff --git a/client/core/rs/src/entities/mod.rs b/client/core/rs/src/entities/mod.rs index 2080e33e4..3276b1c41 100644 --- a/client/core/rs/src/entities/mod.rs +++ b/client/core/rs/src/entities/mod.rs @@ -52,6 +52,8 @@ pub mod provider; pub mod repo; /// Subtypes of [Resource][resource::Resource]. pub mod resource; +/// Subtypes of [Schedule][schedule::Schedule] +pub mod schedule; /// Subtypes of [Server][server::Server]. pub mod server; /// Subtypes of [Stack][stack::Stack] diff --git a/client/core/rs/src/entities/permission.rs b/client/core/rs/src/entities/permission.rs index 1b5251b1d..6ca34629a 100644 --- a/client/core/rs/src/entities/permission.rs +++ b/client/core/rs/src/entities/permission.rs @@ -159,7 +159,7 @@ pub enum SpecificPermission { impl SpecificPermission { fn all() -> IndexSet { - SpecificPermission::VARIANTS.into_iter().cloned().collect() + SpecificPermission::VARIANTS.iter().cloned().collect() } } diff --git a/client/core/rs/src/entities/procedure.rs b/client/core/rs/src/entities/procedure.rs index b9549d6fb..be81721d1 100644 --- a/client/core/rs/src/entities/procedure.rs +++ b/client/core/rs/src/entities/procedure.rs @@ -23,6 +23,8 @@ pub struct ProcedureListItemInfo { pub stages: I64, /// Reflect whether last run successful / currently running. pub state: ProcedureState, + /// Procedure last successful run timestamp in ms. + pub last_run_at: Option, /// If the procedure has schedule enabled, this is the /// next scheduled run time in unix ms. pub next_scheduled_run: Option, diff --git a/client/core/rs/src/entities/repo.rs b/client/core/rs/src/entities/repo.rs index d00067282..fa81498ad 100644 --- a/client/core/rs/src/entities/repo.rs +++ b/client/core/rs/src/entities/repo.rs @@ -40,6 +40,8 @@ pub struct RepoListItemInfo { pub repo: String, /// The configured branch pub branch: String, + /// Full link to the repo. + pub repo_link: String, /// The repo state pub state: RepoState, /// If the repo is cloned, will be the cloned short commit hash. diff --git a/client/core/rs/src/entities/resource.rs b/client/core/rs/src/entities/resource.rs index 73fd4e1d9..3c5161fcf 100644 --- a/client/core/rs/src/entities/resource.rs +++ b/client/core/rs/src/entities/resource.rs @@ -106,6 +106,7 @@ pub struct ResourceQuery { /// Pass Vec of tag ids or tag names #[serde(default, deserialize_with = "string_list_deserializer")] pub tags: Vec, + /// 'All' or 'Any' #[serde(default)] pub tag_behavior: TagBehavior, #[serde(default)] diff --git a/client/core/rs/src/entities/schedule.rs b/client/core/rs/src/entities/schedule.rs new file mode 100644 index 000000000..b122a5cfa --- /dev/null +++ b/client/core/rs/src/entities/schedule.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; +use typeshare::typeshare; + +use crate::entities::{I64, ResourceTarget, ScheduleFormat}; + +/// A scheduled Action / Procedure run. +#[typeshare] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct Schedule { + /// Procedure or Alerter + pub target: ResourceTarget, + /// Readable name of the target resource + pub name: String, + /// The format of the schedule expression + pub schedule_format: ScheduleFormat, + /// The schedule for the run + pub schedule: String, + /// Whether the scheduled run is enabled + pub enabled: bool, + /// Custom schedule timezone if it exists + pub schedule_timezone: String, + /// Last run timestamp in ms. + pub last_run_at: Option, + /// Next scheduled run time in unix ms. + pub next_scheduled_run: Option, + /// If there is an error parsing schedule expression, + /// it will be given here. + pub schedule_error: Option, + /// Resource tags. + pub tags: Vec, +} diff --git a/client/core/rs/src/entities/stack.rs b/client/core/rs/src/entities/stack.rs index 852f9b9f6..3af8fbd03 100644 --- a/client/core/rs/src/entities/stack.rs +++ b/client/core/rs/src/entities/stack.rs @@ -34,14 +34,11 @@ impl Stack { return project_name.clone(); } } - self - .config - .project_name - .is_empty() - .then(|| to_docker_compatible_name(&self.name)) - .unwrap_or_else(|| { - to_docker_compatible_name(&self.config.project_name) - }) + if self.config.project_name.is_empty() { + to_docker_compatible_name(&self.name) + } else { + to_docker_compatible_name(&self.config.project_name) + } } pub fn file_paths(&self) -> &[String] { @@ -77,6 +74,8 @@ pub struct StackListItemInfo { pub repo: String, /// The configured branch pub branch: String, + /// Full link to the repo. + pub repo_link: String, /// The stack state pub state: StackState, /// A string given by docker conveying the status of the stack. @@ -125,6 +124,8 @@ pub struct StackServiceWithUpdate { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum StackState { + /// The stack is currently re/deploying + Deploying, /// All containers are running. Running, /// All containers are paused @@ -143,7 +144,7 @@ pub enum StackState { Unhealthy, /// The stack is not deployed Down, - /// Server not reachable + /// Server not reachable for status #[default] Unknown, } diff --git a/client/core/rs/src/entities/sync.rs b/client/core/rs/src/entities/sync.rs index 01ba51658..0b239a00d 100644 --- a/client/core/rs/src/entities/sync.rs +++ b/client/core/rs/src/entities/sync.rs @@ -39,6 +39,8 @@ pub struct ResourceSyncListItemInfo { pub repo: String, /// The branch of the repo pub branch: String, + /// Full link to the repo. + pub repo_link: String, /// Short commit hash of last sync, or empty string pub last_sync_hash: Option, /// Commit message of last sync, or empty string diff --git a/client/core/ts/package.json b/client/core/ts/package.json index 6a3ec44c0..c3fa2d162 100644 --- a/client/core/ts/package.json +++ b/client/core/ts/package.json @@ -1,6 +1,6 @@ { "name": "komodo_client", - "version": "1.18.0", + "version": "1.18.1", "description": "Komodo client package", "homepage": "https://komo.do", "main": "dist/lib.js", diff --git a/client/core/ts/src/responses.ts b/client/core/ts/src/responses.ts index c18d6194d..2f9a77330 100644 --- a/client/core/ts/src/responses.ts +++ b/client/core/ts/src/responses.ts @@ -50,6 +50,9 @@ export type ReadResponses = { ListActions: Types.ListActionsResponse; ListFullActions: Types.ListFullActionsResponse; + // ==== SCHEDULE ==== + ListSchedules: Types.ListSchedulesResponse; + // ==== SERVER ==== GetServersSummary: Types.GetServersSummaryResponse; GetServer: Types.GetServerResponse; diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index f1b4f43e4..41ca9913c 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -116,12 +116,11 @@ export interface ActionConfig { file_contents?: string; } -export interface ActionInfo { - /** When action was last run */ - last_run_at?: I64; +/** Represents an empty json object: `{}` */ +export interface NoData { } -export type Action = Resource; +export type Action = Resource; export interface ResourceListItem { /** The resource id */ @@ -148,12 +147,12 @@ export enum ActionState { } export interface ActionListItemInfo { - /** Action last run timestamp in ms. */ - last_run_at: I64; /** Whether last action run successful */ state: ActionState; + /** Action last successful run timestamp in ms. */ + last_run_at?: I64; /** - * If the procedure has schedule enabled, this is the + * If the action has schedule enabled, this is the * next scheduled run time in unix ms. */ next_scheduled_run?: I64; @@ -178,6 +177,7 @@ export interface ResourceQuery { names?: string[]; /** Pass Vec of tag ids or tag names */ tags?: string[]; + /** 'All' or 'Any' */ tag_behavior?: TagBehavior; specific?: T; } @@ -270,12 +270,204 @@ export type BatchExecutionResponseItem = export type BatchExecutionResponse = BatchExecutionResponseItem[]; +export enum Operation { + None = "None", + CreateServer = "CreateServer", + UpdateServer = "UpdateServer", + DeleteServer = "DeleteServer", + RenameServer = "RenameServer", + StartContainer = "StartContainer", + RestartContainer = "RestartContainer", + PauseContainer = "PauseContainer", + UnpauseContainer = "UnpauseContainer", + StopContainer = "StopContainer", + DestroyContainer = "DestroyContainer", + StartAllContainers = "StartAllContainers", + RestartAllContainers = "RestartAllContainers", + PauseAllContainers = "PauseAllContainers", + UnpauseAllContainers = "UnpauseAllContainers", + StopAllContainers = "StopAllContainers", + PruneContainers = "PruneContainers", + CreateNetwork = "CreateNetwork", + DeleteNetwork = "DeleteNetwork", + PruneNetworks = "PruneNetworks", + DeleteImage = "DeleteImage", + PruneImages = "PruneImages", + DeleteVolume = "DeleteVolume", + PruneVolumes = "PruneVolumes", + PruneDockerBuilders = "PruneDockerBuilders", + PruneBuildx = "PruneBuildx", + PruneSystem = "PruneSystem", + CreateStack = "CreateStack", + UpdateStack = "UpdateStack", + RenameStack = "RenameStack", + DeleteStack = "DeleteStack", + WriteStackContents = "WriteStackContents", + RefreshStackCache = "RefreshStackCache", + PullStack = "PullStack", + DeployStack = "DeployStack", + StartStack = "StartStack", + RestartStack = "RestartStack", + PauseStack = "PauseStack", + UnpauseStack = "UnpauseStack", + StopStack = "StopStack", + DestroyStack = "DestroyStack", + DeployStackService = "DeployStackService", + PullStackService = "PullStackService", + StartStackService = "StartStackService", + RestartStackService = "RestartStackService", + PauseStackService = "PauseStackService", + UnpauseStackService = "UnpauseStackService", + StopStackService = "StopStackService", + DestroyStackService = "DestroyStackService", + CreateDeployment = "CreateDeployment", + UpdateDeployment = "UpdateDeployment", + RenameDeployment = "RenameDeployment", + DeleteDeployment = "DeleteDeployment", + Deploy = "Deploy", + PullDeployment = "PullDeployment", + StartDeployment = "StartDeployment", + RestartDeployment = "RestartDeployment", + PauseDeployment = "PauseDeployment", + UnpauseDeployment = "UnpauseDeployment", + StopDeployment = "StopDeployment", + DestroyDeployment = "DestroyDeployment", + CreateBuild = "CreateBuild", + UpdateBuild = "UpdateBuild", + RenameBuild = "RenameBuild", + DeleteBuild = "DeleteBuild", + RunBuild = "RunBuild", + CancelBuild = "CancelBuild", + WriteDockerfile = "WriteDockerfile", + CreateRepo = "CreateRepo", + UpdateRepo = "UpdateRepo", + RenameRepo = "RenameRepo", + DeleteRepo = "DeleteRepo", + CloneRepo = "CloneRepo", + PullRepo = "PullRepo", + BuildRepo = "BuildRepo", + CancelRepoBuild = "CancelRepoBuild", + CreateProcedure = "CreateProcedure", + UpdateProcedure = "UpdateProcedure", + RenameProcedure = "RenameProcedure", + DeleteProcedure = "DeleteProcedure", + RunProcedure = "RunProcedure", + CreateAction = "CreateAction", + UpdateAction = "UpdateAction", + RenameAction = "RenameAction", + DeleteAction = "DeleteAction", + RunAction = "RunAction", + CreateBuilder = "CreateBuilder", + UpdateBuilder = "UpdateBuilder", + RenameBuilder = "RenameBuilder", + DeleteBuilder = "DeleteBuilder", + CreateAlerter = "CreateAlerter", + UpdateAlerter = "UpdateAlerter", + RenameAlerter = "RenameAlerter", + DeleteAlerter = "DeleteAlerter", + TestAlerter = "TestAlerter", + CreateResourceSync = "CreateResourceSync", + UpdateResourceSync = "UpdateResourceSync", + RenameResourceSync = "RenameResourceSync", + DeleteResourceSync = "DeleteResourceSync", + WriteSyncContents = "WriteSyncContents", + CommitSync = "CommitSync", + RunSync = "RunSync", + CreateVariable = "CreateVariable", + UpdateVariableValue = "UpdateVariableValue", + DeleteVariable = "DeleteVariable", + CreateGitProviderAccount = "CreateGitProviderAccount", + UpdateGitProviderAccount = "UpdateGitProviderAccount", + DeleteGitProviderAccount = "DeleteGitProviderAccount", + CreateDockerRegistryAccount = "CreateDockerRegistryAccount", + UpdateDockerRegistryAccount = "UpdateDockerRegistryAccount", + DeleteDockerRegistryAccount = "DeleteDockerRegistryAccount", +} + +/** Represents the output of some command being run */ +export interface Log { + /** A label for the log */ + stage: string; + /** The command which was executed */ + command: string; + /** The output of the command in the standard channel */ + stdout: string; + /** The output of the command in the error channel */ + stderr: string; + /** Whether the command run was successful */ + success: boolean; + /** The start time of the command execution */ + start_ts: I64; + /** The end time of the command execution */ + end_ts: I64; +} + +/** An update's status */ +export enum UpdateStatus { + /** The run is in the system but hasn't started yet */ + Queued = "Queued", + /** The run is currently running */ + InProgress = "InProgress", + /** The run is complete */ + Complete = "Complete", +} + export interface Version { major: number; minor: number; patch: number; } +/** Represents an action performed by Komodo. */ +export interface Update { + /** + * The Mongo ID of the update. + * This field is de/serialized from/to JSON as + * `{ "_id": { "$oid": "..." }, ...(rest of serialized Update) }` + */ + _id?: MongoId; + /** The operation performed */ + operation: Operation; + /** The time the operation started */ + start_ts: I64; + /** Whether the operation was successful */ + success: boolean; + /** + * The user id that triggered the update. + * + * Also can take these values for operations triggered automatically: + * - `Procedure`: The operation was triggered as part of a procedure run + * - `Github`: The operation was triggered by a github webhook + * - `Auto Redeploy`: The operation (always `Deploy`) was triggered by an attached build finishing. + */ + operator: string; + /** The target resource to which this update refers */ + target: ResourceTarget; + /** Logs produced as the operation is performed */ + logs: Log[]; + /** The time the operation completed. */ + end_ts?: I64; + /** + * The status of the update + * - `Queued` + * - `InProgress` + * - `Complete` + */ + status: UpdateStatus; + /** An optional version on the update, ie build version or deployed version. */ + version?: Version; + /** An optional commit hash associated with the update, ie cloned hash or deployed hash. */ + commit_hash?: string; + /** Some unstructured, operation specific data. Not for general usage. */ + other_data?: string; + /** If the update is for resource config update, give the previous toml contents */ + prev_toml?: string; + /** If the update is for resource config update, give the current (at time of Update) toml contents */ + current_toml?: string; +} + +export type BoxUpdate = Update; + /** Configuration for an image registry */ export interface ImageRegistryConfig { /** @@ -463,11 +655,13 @@ export interface BuildListItemInfo { /** Whether build is in files on host mode. */ files_on_host: boolean; /** The git provider domain */ - git_provider?: string; + git_provider: string; /** The repo used as the source of the build */ - repo?: string; + repo: string; /** The branch of the repo */ - branch?: string; + branch: string; + /** Full link to the repo. */ + repo_link: string; /** Latest built short commit hash, or null. */ built_hash?: string; /** Latest short commit hash, or null. Only for repo based stacks */ @@ -501,9 +695,10 @@ export type BuilderConfig = export type Builder = Resource; export interface BuilderListItemInfo { - /** 'Server' or 'Aws' */ + /** 'Url', 'Server', or 'Aws' */ builder_type: string; /** + * If 'Url': null * If 'Server': the server id * If 'Aws': the instance type (eg. c5.xlarge) */ @@ -658,10 +853,6 @@ export type Procedure = Resource; export type CopyProcedureResponse = Procedure; -/** Represents an empty json object: `{}` */ -export interface NoData { -} - export type CreateActionWebhookResponse = NoData; /** Response for [CreateApiKey]. */ @@ -985,15 +1176,26 @@ export type Deployment = Resource; * - Running -> running. */ export enum DeploymentState { - Unknown = "unknown", - NotDeployed = "not_deployed", - Created = "created", - Restarting = "restarting", + /** The deployment is currently re/deploying */ + Deploying = "deploying", + /** Container is running */ Running = "running", + /** Container is created but not running */ + Created = "created", + /** Container is in restart loop */ + Restarting = "restarting", + /** Container is being removed */ Removing = "removing", + /** Container is paused */ Paused = "paused", + /** Container is exited */ Exited = "exited", + /** Container is dead */ Dead = "dead", + /** The deployment is not deployed (no matching container) */ + NotDeployed = "not_deployed", + /** Server not reachable for status */ + Unknown = "unknown", } export interface DeploymentListItemInfo { @@ -1304,24 +1506,6 @@ export type GetBuildResponse = Build; export type GetBuilderResponse = Builder; -/** Represents the output of some command being run */ -export interface Log { - /** A label for the log */ - stage: string; - /** The command which was executed */ - command: string; - /** The output of the command in the standard channel */ - stdout: string; - /** The output of the command in the error channel */ - stderr: string; - /** Whether the command run was successful */ - success: boolean; - /** The start time of the command execution */ - start_ts: I64; - /** The end time of the command execution */ - end_ts: I64; -} - export type GetContainerLogResponse = Log; export interface DeploymentActionState { @@ -2146,178 +2330,6 @@ export interface Tag { export type GetTagResponse = Tag; -export enum Operation { - None = "None", - CreateServer = "CreateServer", - UpdateServer = "UpdateServer", - DeleteServer = "DeleteServer", - RenameServer = "RenameServer", - StartContainer = "StartContainer", - RestartContainer = "RestartContainer", - PauseContainer = "PauseContainer", - UnpauseContainer = "UnpauseContainer", - StopContainer = "StopContainer", - DestroyContainer = "DestroyContainer", - StartAllContainers = "StartAllContainers", - RestartAllContainers = "RestartAllContainers", - PauseAllContainers = "PauseAllContainers", - UnpauseAllContainers = "UnpauseAllContainers", - StopAllContainers = "StopAllContainers", - PruneContainers = "PruneContainers", - CreateNetwork = "CreateNetwork", - DeleteNetwork = "DeleteNetwork", - PruneNetworks = "PruneNetworks", - DeleteImage = "DeleteImage", - PruneImages = "PruneImages", - DeleteVolume = "DeleteVolume", - PruneVolumes = "PruneVolumes", - PruneDockerBuilders = "PruneDockerBuilders", - PruneBuildx = "PruneBuildx", - PruneSystem = "PruneSystem", - CreateStack = "CreateStack", - UpdateStack = "UpdateStack", - RenameStack = "RenameStack", - DeleteStack = "DeleteStack", - WriteStackContents = "WriteStackContents", - RefreshStackCache = "RefreshStackCache", - PullStack = "PullStack", - DeployStack = "DeployStack", - StartStack = "StartStack", - RestartStack = "RestartStack", - PauseStack = "PauseStack", - UnpauseStack = "UnpauseStack", - StopStack = "StopStack", - DestroyStack = "DestroyStack", - DeployStackService = "DeployStackService", - PullStackService = "PullStackService", - StartStackService = "StartStackService", - RestartStackService = "RestartStackService", - PauseStackService = "PauseStackService", - UnpauseStackService = "UnpauseStackService", - StopStackService = "StopStackService", - DestroyStackService = "DestroyStackService", - CreateDeployment = "CreateDeployment", - UpdateDeployment = "UpdateDeployment", - RenameDeployment = "RenameDeployment", - DeleteDeployment = "DeleteDeployment", - Deploy = "Deploy", - PullDeployment = "PullDeployment", - StartDeployment = "StartDeployment", - RestartDeployment = "RestartDeployment", - PauseDeployment = "PauseDeployment", - UnpauseDeployment = "UnpauseDeployment", - StopDeployment = "StopDeployment", - DestroyDeployment = "DestroyDeployment", - CreateBuild = "CreateBuild", - UpdateBuild = "UpdateBuild", - RenameBuild = "RenameBuild", - DeleteBuild = "DeleteBuild", - RunBuild = "RunBuild", - CancelBuild = "CancelBuild", - WriteDockerfile = "WriteDockerfile", - CreateRepo = "CreateRepo", - UpdateRepo = "UpdateRepo", - RenameRepo = "RenameRepo", - DeleteRepo = "DeleteRepo", - CloneRepo = "CloneRepo", - PullRepo = "PullRepo", - BuildRepo = "BuildRepo", - CancelRepoBuild = "CancelRepoBuild", - CreateProcedure = "CreateProcedure", - UpdateProcedure = "UpdateProcedure", - RenameProcedure = "RenameProcedure", - DeleteProcedure = "DeleteProcedure", - RunProcedure = "RunProcedure", - CreateAction = "CreateAction", - UpdateAction = "UpdateAction", - RenameAction = "RenameAction", - DeleteAction = "DeleteAction", - RunAction = "RunAction", - CreateBuilder = "CreateBuilder", - UpdateBuilder = "UpdateBuilder", - RenameBuilder = "RenameBuilder", - DeleteBuilder = "DeleteBuilder", - CreateAlerter = "CreateAlerter", - UpdateAlerter = "UpdateAlerter", - RenameAlerter = "RenameAlerter", - DeleteAlerter = "DeleteAlerter", - TestAlerter = "TestAlerter", - CreateResourceSync = "CreateResourceSync", - UpdateResourceSync = "UpdateResourceSync", - RenameResourceSync = "RenameResourceSync", - DeleteResourceSync = "DeleteResourceSync", - WriteSyncContents = "WriteSyncContents", - CommitSync = "CommitSync", - RunSync = "RunSync", - CreateVariable = "CreateVariable", - UpdateVariableValue = "UpdateVariableValue", - DeleteVariable = "DeleteVariable", - CreateGitProviderAccount = "CreateGitProviderAccount", - UpdateGitProviderAccount = "UpdateGitProviderAccount", - DeleteGitProviderAccount = "DeleteGitProviderAccount", - CreateDockerRegistryAccount = "CreateDockerRegistryAccount", - UpdateDockerRegistryAccount = "UpdateDockerRegistryAccount", - DeleteDockerRegistryAccount = "DeleteDockerRegistryAccount", -} - -/** An update's status */ -export enum UpdateStatus { - /** The run is in the system but hasn't started yet */ - Queued = "Queued", - /** The run is currently running */ - InProgress = "InProgress", - /** The run is complete */ - Complete = "Complete", -} - -/** Represents an action performed by Komodo. */ -export interface Update { - /** - * The Mongo ID of the update. - * This field is de/serialized from/to JSON as - * `{ "_id": { "$oid": "..." }, ...(rest of serialized Update) }` - */ - _id?: MongoId; - /** The operation performed */ - operation: Operation; - /** The time the operation started */ - start_ts: I64; - /** Whether the operation was successful */ - success: boolean; - /** - * The user id that triggered the update. - * - * Also can take these values for operations triggered automatically: - * - `Procedure`: The operation was triggered as part of a procedure run - * - `Github`: The operation was triggered by a github webhook - * - `Auto Redeploy`: The operation (always `Deploy`) was triggered by an attached build finishing. - */ - operator: string; - /** The target resource to which this update refers */ - target: ResourceTarget; - /** Logs produced as the operation is performed */ - logs: Log[]; - /** The time the operation completed. */ - end_ts?: I64; - /** - * The status of the update - * - `Queued` - * - `InProgress` - * - `Complete` - */ - status: UpdateStatus; - /** An optional version on the update, ie build version or deployed version. */ - version?: Version; - /** An optional commit hash associated with the update, ie cloned hash or deployed hash. */ - commit_hash?: string; - /** Some unstructured, operation specific data. Not for general usage. */ - other_data?: string; - /** If the update is for resource config update, give the previous toml contents */ - prev_toml?: string; - /** If the update is for resource config update, give the current (at time of Update) toml contents */ - current_toml?: string; -} - export type GetUpdateResponse = Update; /** @@ -3411,6 +3423,8 @@ export interface ProcedureListItemInfo { stages: I64; /** Reflect whether last run successful / currently running. */ state: ProcedureState; + /** Procedure last successful run timestamp in ms. */ + last_run_at?: I64; /** * If the procedure has schedule enabled, this is the * next scheduled run time in unix ms. @@ -3457,6 +3471,8 @@ export interface RepoListItemInfo { repo: string; /** The configured branch */ branch: string; + /** Full link to the repo. */ + repo_link: string; /** The repo state */ state: RepoState; /** If the repo is cloned, will be the cloned short commit hash. */ @@ -3503,6 +3519,8 @@ export interface ResourceSyncListItemInfo { repo: string; /** The branch of the repo */ branch: string; + /** Full link to the repo. */ + repo_link: string; /** Short commit hash of last sync, or empty string */ last_sync_hash?: string; /** Commit message of last sync, or empty string */ @@ -3515,6 +3533,35 @@ export type ResourceSyncListItem = ResourceListItem; export type ListResourceSyncsResponse = ResourceSyncListItem[]; +/** A scheduled Action / Procedure run. */ +export interface Schedule { + /** Procedure or Alerter */ + target: ResourceTarget; + /** Readable name of the target resource */ + name: string; + /** The format of the schedule expression */ + schedule_format: ScheduleFormat; + /** The schedule for the run */ + schedule: string; + /** Whether the scheduled run is enabled */ + enabled: boolean; + /** Custom schedule timezone if it exists */ + schedule_timezone: string; + /** Last run timestamp in ms. */ + last_run_at?: I64; + /** Next scheduled run time in unix ms. */ + next_scheduled_run?: I64; + /** + * If there is an error parsing schedule expression, + * it will be given here. + */ + schedule_error?: string; + /** Resource tags. */ + tags: string[]; +} + +export type ListSchedulesResponse = Schedule[]; + export type ListSecretsResponse = string[]; export enum ServerState { @@ -3565,6 +3612,8 @@ export interface StackService { export type ListStackServicesResponse = StackService[]; export enum StackState { + /** The stack is currently re/deploying */ + Deploying = "deploying", /** All containers are running. */ Running = "running", /** All containers are paused */ @@ -3583,7 +3632,7 @@ export enum StackState { Unhealthy = "unhealthy", /** The stack is not deployed */ Down = "down", - /** Server not reachable */ + /** Server not reachable for status */ Unknown = "unknown", } @@ -3608,6 +3657,8 @@ export interface StackListItemInfo { repo: string; /** The configured branch */ branch: string; + /** Full link to the repo. */ + repo_link: string; /** The stack state */ state: StackState; /** A string given by docker conveying the status of the stack. */ @@ -6321,6 +6372,17 @@ export interface ListResourceSyncs { query?: ResourceSyncQuery; } +/** + * List configured schedules. + * Response: [ListSchedulesResponse]. + */ +export interface ListSchedules { + /** Pass Vec of tag ids or tag names */ + tags?: string[]; + /** 'All' or 'Any' */ + tag_behavior?: TagBehavior; +} + /** * List the available secrets from the core config. * Response: [ListSecretsResponse]. @@ -6562,9 +6624,9 @@ export interface PermissionToml { * - Execute * - Write */ - level: PermissionLevel; + level?: PermissionLevel; /** Any [SpecificPermissions](SpecificPermission) on the resource */ - specific: Array; + specific?: Array; } export enum PortTypeEnum { @@ -7783,6 +7845,7 @@ export type ReadRequest = | { type: "GetActionActionState", params: GetActionActionState } | { type: "ListActions", params: ListActions } | { type: "ListFullActions", params: ListFullActions } + | { type: "ListSchedules", params: ListSchedules } | { type: "GetServersSummary", params: GetServersSummary } | { type: "GetServer", params: GetServer } | { type: "GetServerState", params: GetServerState } @@ -7897,9 +7960,9 @@ export enum SpecificPermission { Attach = "Attach", /** * On **Server** - * - Access the `docker inspect` apis + * - Access the `container inspect` apis * On **Stack / Deployment** - * - Access `docker inspect $container` for associated containers + * - Access `container inspect` apis for associated containers */ Inspect = "Inspect", /** diff --git a/client/periphery/rs/src/api/git.rs b/client/periphery/rs/src/api/git.rs index f36c10785..c26823eb6 100644 --- a/client/periphery/rs/src/api/git.rs +++ b/client/periphery/rs/src/api/git.rs @@ -6,8 +6,9 @@ use komodo_client::entities::{ use resolver_api::Resolve; use serde::{Deserialize, Serialize}; +/// Returns `null` if not a repo #[derive(Debug, Clone, Serialize, Deserialize, Resolve)] -#[response(LatestCommit)] +#[response(Option)] #[error(serror::Error)] pub struct GetLatestCommit { pub name: String, diff --git a/frontend/public/client/responses.d.ts b/frontend/public/client/responses.d.ts index e11cb751d..f2510f711 100644 --- a/frontend/public/client/responses.d.ts +++ b/frontend/public/client/responses.d.ts @@ -38,6 +38,7 @@ export type ReadResponses = { GetActionActionState: Types.GetActionActionStateResponse; ListActions: Types.ListActionsResponse; ListFullActions: Types.ListFullActionsResponse; + ListSchedules: Types.ListSchedulesResponse; GetServersSummary: Types.GetServersSummaryResponse; GetServer: Types.GetServerResponse; GetServerState: Types.GetServerStateResponse; diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 4b07671e8..2a6f80e50 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -104,11 +104,10 @@ export interface ActionConfig { */ file_contents?: string; } -export interface ActionInfo { - /** When action was last run */ - last_run_at?: I64; +/** Represents an empty json object: `{}` */ +export interface NoData { } -export type Action = Resource; +export type Action = Resource; export interface ResourceListItem { /** The resource id */ id: string; @@ -132,12 +131,12 @@ export declare enum ActionState { Running = "Running" } export interface ActionListItemInfo { - /** Action last run timestamp in ms. */ - last_run_at: I64; /** Whether last action run successful */ state: ActionState; + /** Action last successful run timestamp in ms. */ + last_run_at?: I64; /** - * If the procedure has schedule enabled, this is the + * If the action has schedule enabled, this is the * next scheduled run time in unix ms. */ next_scheduled_run?: I64; @@ -159,6 +158,7 @@ export interface ResourceQuery { names?: string[]; /** Pass Vec of tag ids or tag names */ tags?: string[]; + /** 'All' or 'Any' */ tag_behavior?: TagBehavior; specific?: T; } @@ -279,11 +279,198 @@ export type BatchExecutionResponseItem = { data: BatchExecutionResponseItemErr; }; export type BatchExecutionResponse = BatchExecutionResponseItem[]; +export declare enum Operation { + None = "None", + CreateServer = "CreateServer", + UpdateServer = "UpdateServer", + DeleteServer = "DeleteServer", + RenameServer = "RenameServer", + StartContainer = "StartContainer", + RestartContainer = "RestartContainer", + PauseContainer = "PauseContainer", + UnpauseContainer = "UnpauseContainer", + StopContainer = "StopContainer", + DestroyContainer = "DestroyContainer", + StartAllContainers = "StartAllContainers", + RestartAllContainers = "RestartAllContainers", + PauseAllContainers = "PauseAllContainers", + UnpauseAllContainers = "UnpauseAllContainers", + StopAllContainers = "StopAllContainers", + PruneContainers = "PruneContainers", + CreateNetwork = "CreateNetwork", + DeleteNetwork = "DeleteNetwork", + PruneNetworks = "PruneNetworks", + DeleteImage = "DeleteImage", + PruneImages = "PruneImages", + DeleteVolume = "DeleteVolume", + PruneVolumes = "PruneVolumes", + PruneDockerBuilders = "PruneDockerBuilders", + PruneBuildx = "PruneBuildx", + PruneSystem = "PruneSystem", + CreateStack = "CreateStack", + UpdateStack = "UpdateStack", + RenameStack = "RenameStack", + DeleteStack = "DeleteStack", + WriteStackContents = "WriteStackContents", + RefreshStackCache = "RefreshStackCache", + PullStack = "PullStack", + DeployStack = "DeployStack", + StartStack = "StartStack", + RestartStack = "RestartStack", + PauseStack = "PauseStack", + UnpauseStack = "UnpauseStack", + StopStack = "StopStack", + DestroyStack = "DestroyStack", + DeployStackService = "DeployStackService", + PullStackService = "PullStackService", + StartStackService = "StartStackService", + RestartStackService = "RestartStackService", + PauseStackService = "PauseStackService", + UnpauseStackService = "UnpauseStackService", + StopStackService = "StopStackService", + DestroyStackService = "DestroyStackService", + CreateDeployment = "CreateDeployment", + UpdateDeployment = "UpdateDeployment", + RenameDeployment = "RenameDeployment", + DeleteDeployment = "DeleteDeployment", + Deploy = "Deploy", + PullDeployment = "PullDeployment", + StartDeployment = "StartDeployment", + RestartDeployment = "RestartDeployment", + PauseDeployment = "PauseDeployment", + UnpauseDeployment = "UnpauseDeployment", + StopDeployment = "StopDeployment", + DestroyDeployment = "DestroyDeployment", + CreateBuild = "CreateBuild", + UpdateBuild = "UpdateBuild", + RenameBuild = "RenameBuild", + DeleteBuild = "DeleteBuild", + RunBuild = "RunBuild", + CancelBuild = "CancelBuild", + WriteDockerfile = "WriteDockerfile", + CreateRepo = "CreateRepo", + UpdateRepo = "UpdateRepo", + RenameRepo = "RenameRepo", + DeleteRepo = "DeleteRepo", + CloneRepo = "CloneRepo", + PullRepo = "PullRepo", + BuildRepo = "BuildRepo", + CancelRepoBuild = "CancelRepoBuild", + CreateProcedure = "CreateProcedure", + UpdateProcedure = "UpdateProcedure", + RenameProcedure = "RenameProcedure", + DeleteProcedure = "DeleteProcedure", + RunProcedure = "RunProcedure", + CreateAction = "CreateAction", + UpdateAction = "UpdateAction", + RenameAction = "RenameAction", + DeleteAction = "DeleteAction", + RunAction = "RunAction", + CreateBuilder = "CreateBuilder", + UpdateBuilder = "UpdateBuilder", + RenameBuilder = "RenameBuilder", + DeleteBuilder = "DeleteBuilder", + CreateAlerter = "CreateAlerter", + UpdateAlerter = "UpdateAlerter", + RenameAlerter = "RenameAlerter", + DeleteAlerter = "DeleteAlerter", + TestAlerter = "TestAlerter", + CreateResourceSync = "CreateResourceSync", + UpdateResourceSync = "UpdateResourceSync", + RenameResourceSync = "RenameResourceSync", + DeleteResourceSync = "DeleteResourceSync", + WriteSyncContents = "WriteSyncContents", + CommitSync = "CommitSync", + RunSync = "RunSync", + CreateVariable = "CreateVariable", + UpdateVariableValue = "UpdateVariableValue", + DeleteVariable = "DeleteVariable", + CreateGitProviderAccount = "CreateGitProviderAccount", + UpdateGitProviderAccount = "UpdateGitProviderAccount", + DeleteGitProviderAccount = "DeleteGitProviderAccount", + CreateDockerRegistryAccount = "CreateDockerRegistryAccount", + UpdateDockerRegistryAccount = "UpdateDockerRegistryAccount", + DeleteDockerRegistryAccount = "DeleteDockerRegistryAccount" +} +/** Represents the output of some command being run */ +export interface Log { + /** A label for the log */ + stage: string; + /** The command which was executed */ + command: string; + /** The output of the command in the standard channel */ + stdout: string; + /** The output of the command in the error channel */ + stderr: string; + /** Whether the command run was successful */ + success: boolean; + /** The start time of the command execution */ + start_ts: I64; + /** The end time of the command execution */ + end_ts: I64; +} +/** An update's status */ +export declare enum UpdateStatus { + /** The run is in the system but hasn't started yet */ + Queued = "Queued", + /** The run is currently running */ + InProgress = "InProgress", + /** The run is complete */ + Complete = "Complete" +} export interface Version { major: number; minor: number; patch: number; } +/** Represents an action performed by Komodo. */ +export interface Update { + /** + * The Mongo ID of the update. + * This field is de/serialized from/to JSON as + * `{ "_id": { "$oid": "..." }, ...(rest of serialized Update) }` + */ + _id?: MongoId; + /** The operation performed */ + operation: Operation; + /** The time the operation started */ + start_ts: I64; + /** Whether the operation was successful */ + success: boolean; + /** + * The user id that triggered the update. + * + * Also can take these values for operations triggered automatically: + * - `Procedure`: The operation was triggered as part of a procedure run + * - `Github`: The operation was triggered by a github webhook + * - `Auto Redeploy`: The operation (always `Deploy`) was triggered by an attached build finishing. + */ + operator: string; + /** The target resource to which this update refers */ + target: ResourceTarget; + /** Logs produced as the operation is performed */ + logs: Log[]; + /** The time the operation completed. */ + end_ts?: I64; + /** + * The status of the update + * - `Queued` + * - `InProgress` + * - `Complete` + */ + status: UpdateStatus; + /** An optional version on the update, ie build version or deployed version. */ + version?: Version; + /** An optional commit hash associated with the update, ie cloned hash or deployed hash. */ + commit_hash?: string; + /** Some unstructured, operation specific data. Not for general usage. */ + other_data?: string; + /** If the update is for resource config update, give the previous toml contents */ + prev_toml?: string; + /** If the update is for resource config update, give the current (at time of Update) toml contents */ + current_toml?: string; +} +export type BoxUpdate = Update; /** Configuration for an image registry */ export interface ImageRegistryConfig { /** @@ -465,11 +652,13 @@ export interface BuildListItemInfo { /** Whether build is in files on host mode. */ files_on_host: boolean; /** The git provider domain */ - git_provider?: string; + git_provider: string; /** The repo used as the source of the build */ - repo?: string; + repo: string; /** The branch of the repo */ - branch?: string; + branch: string; + /** Full link to the repo. */ + repo_link: string; /** Latest built short commit hash, or null. */ built_hash?: string; /** Latest short commit hash, or null. Only for repo based stacks */ @@ -506,9 +695,10 @@ export type BuilderConfig = }; export type Builder = Resource; export interface BuilderListItemInfo { - /** 'Server' or 'Aws' */ + /** 'Url', 'Server', or 'Aws' */ builder_type: string; /** + * If 'Url': null * If 'Server': the server id * If 'Aws': the instance type (eg. c5.xlarge) */ @@ -780,9 +970,6 @@ export interface ProcedureConfig { */ export type Procedure = Resource; export type CopyProcedureResponse = Procedure; -/** Represents an empty json object: `{}` */ -export interface NoData { -} export type CreateActionWebhookResponse = NoData; /** Response for [CreateApiKey]. */ export interface CreateApiKeyResponse { @@ -1091,15 +1278,26 @@ export type Deployment = Resource; * - Running -> running. */ export declare enum DeploymentState { - Unknown = "unknown", - NotDeployed = "not_deployed", - Created = "created", - Restarting = "restarting", + /** The deployment is currently re/deploying */ + Deploying = "deploying", + /** Container is running */ Running = "running", + /** Container is created but not running */ + Created = "created", + /** Container is in restart loop */ + Restarting = "restarting", + /** Container is being removed */ Removing = "removing", + /** Container is paused */ Paused = "paused", + /** Container is exited */ Exited = "exited", - Dead = "dead" + /** Container is dead */ + Dead = "dead", + /** The deployment is not deployed (no matching container) */ + NotDeployed = "not_deployed", + /** Server not reachable for status */ + Unknown = "unknown" } export interface DeploymentListItemInfo { /** The state of the deployment / underlying docker container. */ @@ -1444,23 +1642,6 @@ export interface BuildActionState { export type GetBuildActionStateResponse = BuildActionState; export type GetBuildResponse = Build; export type GetBuilderResponse = Builder; -/** Represents the output of some command being run */ -export interface Log { - /** A label for the log */ - stage: string; - /** The command which was executed */ - command: string; - /** The output of the command in the standard channel */ - stdout: string; - /** The output of the command in the error channel */ - stderr: string; - /** Whether the command run was successful */ - success: boolean; - /** The start time of the command execution */ - start_ts: I64; - /** The end time of the command execution */ - end_ts: I64; -} export type GetContainerLogResponse = Log; export interface DeploymentActionState { pulling: boolean; @@ -2240,175 +2421,6 @@ export interface Tag { owner?: string; } export type GetTagResponse = Tag; -export declare enum Operation { - None = "None", - CreateServer = "CreateServer", - UpdateServer = "UpdateServer", - DeleteServer = "DeleteServer", - RenameServer = "RenameServer", - StartContainer = "StartContainer", - RestartContainer = "RestartContainer", - PauseContainer = "PauseContainer", - UnpauseContainer = "UnpauseContainer", - StopContainer = "StopContainer", - DestroyContainer = "DestroyContainer", - StartAllContainers = "StartAllContainers", - RestartAllContainers = "RestartAllContainers", - PauseAllContainers = "PauseAllContainers", - UnpauseAllContainers = "UnpauseAllContainers", - StopAllContainers = "StopAllContainers", - PruneContainers = "PruneContainers", - CreateNetwork = "CreateNetwork", - DeleteNetwork = "DeleteNetwork", - PruneNetworks = "PruneNetworks", - DeleteImage = "DeleteImage", - PruneImages = "PruneImages", - DeleteVolume = "DeleteVolume", - PruneVolumes = "PruneVolumes", - PruneDockerBuilders = "PruneDockerBuilders", - PruneBuildx = "PruneBuildx", - PruneSystem = "PruneSystem", - CreateStack = "CreateStack", - UpdateStack = "UpdateStack", - RenameStack = "RenameStack", - DeleteStack = "DeleteStack", - WriteStackContents = "WriteStackContents", - RefreshStackCache = "RefreshStackCache", - PullStack = "PullStack", - DeployStack = "DeployStack", - StartStack = "StartStack", - RestartStack = "RestartStack", - PauseStack = "PauseStack", - UnpauseStack = "UnpauseStack", - StopStack = "StopStack", - DestroyStack = "DestroyStack", - DeployStackService = "DeployStackService", - PullStackService = "PullStackService", - StartStackService = "StartStackService", - RestartStackService = "RestartStackService", - PauseStackService = "PauseStackService", - UnpauseStackService = "UnpauseStackService", - StopStackService = "StopStackService", - DestroyStackService = "DestroyStackService", - CreateDeployment = "CreateDeployment", - UpdateDeployment = "UpdateDeployment", - RenameDeployment = "RenameDeployment", - DeleteDeployment = "DeleteDeployment", - Deploy = "Deploy", - PullDeployment = "PullDeployment", - StartDeployment = "StartDeployment", - RestartDeployment = "RestartDeployment", - PauseDeployment = "PauseDeployment", - UnpauseDeployment = "UnpauseDeployment", - StopDeployment = "StopDeployment", - DestroyDeployment = "DestroyDeployment", - CreateBuild = "CreateBuild", - UpdateBuild = "UpdateBuild", - RenameBuild = "RenameBuild", - DeleteBuild = "DeleteBuild", - RunBuild = "RunBuild", - CancelBuild = "CancelBuild", - WriteDockerfile = "WriteDockerfile", - CreateRepo = "CreateRepo", - UpdateRepo = "UpdateRepo", - RenameRepo = "RenameRepo", - DeleteRepo = "DeleteRepo", - CloneRepo = "CloneRepo", - PullRepo = "PullRepo", - BuildRepo = "BuildRepo", - CancelRepoBuild = "CancelRepoBuild", - CreateProcedure = "CreateProcedure", - UpdateProcedure = "UpdateProcedure", - RenameProcedure = "RenameProcedure", - DeleteProcedure = "DeleteProcedure", - RunProcedure = "RunProcedure", - CreateAction = "CreateAction", - UpdateAction = "UpdateAction", - RenameAction = "RenameAction", - DeleteAction = "DeleteAction", - RunAction = "RunAction", - CreateBuilder = "CreateBuilder", - UpdateBuilder = "UpdateBuilder", - RenameBuilder = "RenameBuilder", - DeleteBuilder = "DeleteBuilder", - CreateAlerter = "CreateAlerter", - UpdateAlerter = "UpdateAlerter", - RenameAlerter = "RenameAlerter", - DeleteAlerter = "DeleteAlerter", - TestAlerter = "TestAlerter", - CreateResourceSync = "CreateResourceSync", - UpdateResourceSync = "UpdateResourceSync", - RenameResourceSync = "RenameResourceSync", - DeleteResourceSync = "DeleteResourceSync", - WriteSyncContents = "WriteSyncContents", - CommitSync = "CommitSync", - RunSync = "RunSync", - CreateVariable = "CreateVariable", - UpdateVariableValue = "UpdateVariableValue", - DeleteVariable = "DeleteVariable", - CreateGitProviderAccount = "CreateGitProviderAccount", - UpdateGitProviderAccount = "UpdateGitProviderAccount", - DeleteGitProviderAccount = "DeleteGitProviderAccount", - CreateDockerRegistryAccount = "CreateDockerRegistryAccount", - UpdateDockerRegistryAccount = "UpdateDockerRegistryAccount", - DeleteDockerRegistryAccount = "DeleteDockerRegistryAccount" -} -/** An update's status */ -export declare enum UpdateStatus { - /** The run is in the system but hasn't started yet */ - Queued = "Queued", - /** The run is currently running */ - InProgress = "InProgress", - /** The run is complete */ - Complete = "Complete" -} -/** Represents an action performed by Komodo. */ -export interface Update { - /** - * The Mongo ID of the update. - * This field is de/serialized from/to JSON as - * `{ "_id": { "$oid": "..." }, ...(rest of serialized Update) }` - */ - _id?: MongoId; - /** The operation performed */ - operation: Operation; - /** The time the operation started */ - start_ts: I64; - /** Whether the operation was successful */ - success: boolean; - /** - * The user id that triggered the update. - * - * Also can take these values for operations triggered automatically: - * - `Procedure`: The operation was triggered as part of a procedure run - * - `Github`: The operation was triggered by a github webhook - * - `Auto Redeploy`: The operation (always `Deploy`) was triggered by an attached build finishing. - */ - operator: string; - /** The target resource to which this update refers */ - target: ResourceTarget; - /** Logs produced as the operation is performed */ - logs: Log[]; - /** The time the operation completed. */ - end_ts?: I64; - /** - * The status of the update - * - `Queued` - * - `InProgress` - * - `Complete` - */ - status: UpdateStatus; - /** An optional version on the update, ie build version or deployed version. */ - version?: Version; - /** An optional commit hash associated with the update, ie cloned hash or deployed hash. */ - commit_hash?: string; - /** Some unstructured, operation specific data. Not for general usage. */ - other_data?: string; - /** If the update is for resource config update, give the previous toml contents */ - prev_toml?: string; - /** If the update is for resource config update, give the current (at time of Update) toml contents */ - current_toml?: string; -} export type GetUpdateResponse = Update; /** * Permission users at the group level. @@ -3390,6 +3402,8 @@ export interface ProcedureListItemInfo { stages: I64; /** Reflect whether last run successful / currently running. */ state: ProcedureState; + /** Procedure last successful run timestamp in ms. */ + last_run_at?: I64; /** * If the procedure has schedule enabled, this is the * next scheduled run time in unix ms. @@ -3432,6 +3446,8 @@ export interface RepoListItemInfo { repo: string; /** The configured branch */ branch: string; + /** Full link to the repo. */ + repo_link: string; /** The repo state */ state: RepoState; /** If the repo is cloned, will be the cloned short commit hash. */ @@ -3474,6 +3490,8 @@ export interface ResourceSyncListItemInfo { repo: string; /** The branch of the repo */ branch: string; + /** Full link to the repo. */ + repo_link: string; /** Short commit hash of last sync, or empty string */ last_sync_hash?: string; /** Commit message of last sync, or empty string */ @@ -3483,6 +3501,33 @@ export interface ResourceSyncListItemInfo { } export type ResourceSyncListItem = ResourceListItem; export type ListResourceSyncsResponse = ResourceSyncListItem[]; +/** A scheduled Action / Procedure run. */ +export interface Schedule { + /** Procedure or Alerter */ + target: ResourceTarget; + /** Readable name of the target resource */ + name: string; + /** The format of the schedule expression */ + schedule_format: ScheduleFormat; + /** The schedule for the run */ + schedule: string; + /** Whether the scheduled run is enabled */ + enabled: boolean; + /** Custom schedule timezone if it exists */ + schedule_timezone: string; + /** Last run timestamp in ms. */ + last_run_at?: I64; + /** Next scheduled run time in unix ms. */ + next_scheduled_run?: I64; + /** + * If there is an error parsing schedule expression, + * it will be given here. + */ + schedule_error?: string; + /** Resource tags. */ + tags: string[]; +} +export type ListSchedulesResponse = Schedule[]; export type ListSecretsResponse = string[]; export declare enum ServerState { /** Server is unreachable. */ @@ -3526,6 +3571,8 @@ export interface StackService { } export type ListStackServicesResponse = StackService[]; export declare enum StackState { + /** The stack is currently re/deploying */ + Deploying = "deploying", /** All containers are running. */ Running = "running", /** All containers are paused */ @@ -3544,7 +3591,7 @@ export declare enum StackState { Unhealthy = "unhealthy", /** The stack is not deployed */ Down = "down", - /** Server not reachable */ + /** Server not reachable for status */ Unknown = "unknown" } export interface StackServiceWithUpdate { @@ -3567,6 +3614,8 @@ export interface StackListItemInfo { repo: string; /** The configured branch */ branch: string; + /** Full link to the repo. */ + repo_link: string; /** The stack state */ state: StackState; /** A string given by docker conveying the status of the stack. */ @@ -5984,6 +6033,16 @@ export interface ListResourceSyncs { /** optional structured query to filter syncs. */ query?: ResourceSyncQuery; } +/** + * List configured schedules. + * Response: [ListSchedulesResponse]. + */ +export interface ListSchedules { + /** Pass Vec of tag ids or tag names */ + tags?: string[]; + /** 'All' or 'Any' */ + tag_behavior?: TagBehavior; +} /** * List the available secrets from the core config. * Response: [ListSecretsResponse]. @@ -6204,9 +6263,9 @@ export interface PermissionToml { * - Execute * - Write */ - level: PermissionLevel; + level?: PermissionLevel; /** Any [SpecificPermissions](SpecificPermission) on the resource */ - specific: Array; + specific?: Array; } export declare enum PortTypeEnum { EMPTY = "", @@ -7507,6 +7566,9 @@ export type ReadRequest = { } | { type: "ListFullActions"; params: ListFullActions; +} | { + type: "ListSchedules"; + params: ListSchedules; } | { type: "GetServersSummary"; params: GetServersSummary; @@ -7807,9 +7869,9 @@ export declare enum SpecificPermission { Attach = "Attach", /** * On **Server** - * - Access the `docker inspect` apis + * - Access the `container inspect` apis * On **Stack / Deployment** - * - Access `docker inspect $container` for associated containers + * - Access `container inspect` apis for associated containers */ Inspect = "Inspect", /** diff --git a/frontend/public/client/types.js b/frontend/public/client/types.js index 505f60902..2bb8d23c8 100644 --- a/frontend/public/client/types.js +++ b/frontend/public/client/types.js @@ -36,142 +36,6 @@ export var TagBehavior; /** Returns resources which have one or more of the tags */ TagBehavior["Any"] = "Any"; })(TagBehavior || (TagBehavior = {})); -export var BuildState; -(function (BuildState) { - /** Last build successful (or never built) */ - BuildState["Ok"] = "Ok"; - /** Last build failed */ - BuildState["Failed"] = "Failed"; - /** Currently building */ - BuildState["Building"] = "Building"; - /** Other case */ - BuildState["Unknown"] = "Unknown"; -})(BuildState || (BuildState = {})); -export var RestartMode; -(function (RestartMode) { - RestartMode["NoRestart"] = "no"; - RestartMode["OnFailure"] = "on-failure"; - RestartMode["Always"] = "always"; - RestartMode["UnlessStopped"] = "unless-stopped"; -})(RestartMode || (RestartMode = {})); -export var TerminationSignal; -(function (TerminationSignal) { - TerminationSignal["SigHup"] = "SIGHUP"; - TerminationSignal["SigInt"] = "SIGINT"; - TerminationSignal["SigQuit"] = "SIGQUIT"; - TerminationSignal["SigTerm"] = "SIGTERM"; -})(TerminationSignal || (TerminationSignal = {})); -/** - * Variants de/serialized from/to snake_case. - * - * Eg. - * - NotDeployed -> not_deployed - * - Restarting -> restarting - * - Running -> running. - */ -export var DeploymentState; -(function (DeploymentState) { - DeploymentState["Unknown"] = "unknown"; - DeploymentState["NotDeployed"] = "not_deployed"; - DeploymentState["Created"] = "created"; - DeploymentState["Restarting"] = "restarting"; - DeploymentState["Running"] = "running"; - DeploymentState["Removing"] = "removing"; - DeploymentState["Paused"] = "paused"; - DeploymentState["Exited"] = "exited"; - DeploymentState["Dead"] = "dead"; -})(DeploymentState || (DeploymentState = {})); -/** Severity level of problem. */ -export var SeverityLevel; -(function (SeverityLevel) { - /** No problem. */ - SeverityLevel["Ok"] = "OK"; - /** Problem is imminent. */ - SeverityLevel["Warning"] = "WARNING"; - /** Problem fully realized. */ - SeverityLevel["Critical"] = "CRITICAL"; -})(SeverityLevel || (SeverityLevel = {})); -export var Timelength; -(function (Timelength) { - Timelength["OneSecond"] = "1-sec"; - Timelength["FiveSeconds"] = "5-sec"; - Timelength["TenSeconds"] = "10-sec"; - Timelength["FifteenSeconds"] = "15-sec"; - Timelength["ThirtySeconds"] = "30-sec"; - Timelength["OneMinute"] = "1-min"; - Timelength["TwoMinutes"] = "2-min"; - Timelength["FiveMinutes"] = "5-min"; - Timelength["TenMinutes"] = "10-min"; - Timelength["FifteenMinutes"] = "15-min"; - Timelength["ThirtyMinutes"] = "30-min"; - Timelength["OneHour"] = "1-hr"; - Timelength["TwoHours"] = "2-hr"; - Timelength["SixHours"] = "6-hr"; - Timelength["EightHours"] = "8-hr"; - Timelength["TwelveHours"] = "12-hr"; - Timelength["OneDay"] = "1-day"; - Timelength["ThreeDay"] = "3-day"; - Timelength["OneWeek"] = "1-wk"; - Timelength["TwoWeeks"] = "2-wk"; - Timelength["ThirtyDays"] = "30-day"; -})(Timelength || (Timelength = {})); -export var TagColor; -(function (TagColor) { - TagColor["LightSlate"] = "LightSlate"; - TagColor["Slate"] = "Slate"; - TagColor["DarkSlate"] = "DarkSlate"; - TagColor["LightRed"] = "LightRed"; - TagColor["Red"] = "Red"; - TagColor["DarkRed"] = "DarkRed"; - TagColor["LightOrange"] = "LightOrange"; - TagColor["Orange"] = "Orange"; - TagColor["DarkOrange"] = "DarkOrange"; - TagColor["LightAmber"] = "LightAmber"; - TagColor["Amber"] = "Amber"; - TagColor["DarkAmber"] = "DarkAmber"; - TagColor["LightYellow"] = "LightYellow"; - TagColor["Yellow"] = "Yellow"; - TagColor["DarkYellow"] = "DarkYellow"; - TagColor["LightLime"] = "LightLime"; - TagColor["Lime"] = "Lime"; - TagColor["DarkLime"] = "DarkLime"; - TagColor["LightGreen"] = "LightGreen"; - TagColor["Green"] = "Green"; - TagColor["DarkGreen"] = "DarkGreen"; - TagColor["LightEmerald"] = "LightEmerald"; - TagColor["Emerald"] = "Emerald"; - TagColor["DarkEmerald"] = "DarkEmerald"; - TagColor["LightTeal"] = "LightTeal"; - TagColor["Teal"] = "Teal"; - TagColor["DarkTeal"] = "DarkTeal"; - TagColor["LightCyan"] = "LightCyan"; - TagColor["Cyan"] = "Cyan"; - TagColor["DarkCyan"] = "DarkCyan"; - TagColor["LightSky"] = "LightSky"; - TagColor["Sky"] = "Sky"; - TagColor["DarkSky"] = "DarkSky"; - TagColor["LightBlue"] = "LightBlue"; - TagColor["Blue"] = "Blue"; - TagColor["DarkBlue"] = "DarkBlue"; - TagColor["LightIndigo"] = "LightIndigo"; - TagColor["Indigo"] = "Indigo"; - TagColor["DarkIndigo"] = "DarkIndigo"; - TagColor["LightViolet"] = "LightViolet"; - TagColor["Violet"] = "Violet"; - TagColor["DarkViolet"] = "DarkViolet"; - TagColor["LightPurple"] = "LightPurple"; - TagColor["Purple"] = "Purple"; - TagColor["DarkPurple"] = "DarkPurple"; - TagColor["LightFuchsia"] = "LightFuchsia"; - TagColor["Fuchsia"] = "Fuchsia"; - TagColor["DarkFuchsia"] = "DarkFuchsia"; - TagColor["LightPink"] = "LightPink"; - TagColor["Pink"] = "Pink"; - TagColor["DarkPink"] = "DarkPink"; - TagColor["LightRose"] = "LightRose"; - TagColor["Rose"] = "Rose"; - TagColor["DarkRose"] = "DarkRose"; -})(TagColor || (TagColor = {})); export var Operation; (function (Operation) { Operation["None"] = "None"; @@ -296,6 +160,153 @@ export var UpdateStatus; /** The run is complete */ UpdateStatus["Complete"] = "Complete"; })(UpdateStatus || (UpdateStatus = {})); +export var BuildState; +(function (BuildState) { + /** Last build successful (or never built) */ + BuildState["Ok"] = "Ok"; + /** Last build failed */ + BuildState["Failed"] = "Failed"; + /** Currently building */ + BuildState["Building"] = "Building"; + /** Other case */ + BuildState["Unknown"] = "Unknown"; +})(BuildState || (BuildState = {})); +export var RestartMode; +(function (RestartMode) { + RestartMode["NoRestart"] = "no"; + RestartMode["OnFailure"] = "on-failure"; + RestartMode["Always"] = "always"; + RestartMode["UnlessStopped"] = "unless-stopped"; +})(RestartMode || (RestartMode = {})); +export var TerminationSignal; +(function (TerminationSignal) { + TerminationSignal["SigHup"] = "SIGHUP"; + TerminationSignal["SigInt"] = "SIGINT"; + TerminationSignal["SigQuit"] = "SIGQUIT"; + TerminationSignal["SigTerm"] = "SIGTERM"; +})(TerminationSignal || (TerminationSignal = {})); +/** + * Variants de/serialized from/to snake_case. + * + * Eg. + * - NotDeployed -> not_deployed + * - Restarting -> restarting + * - Running -> running. + */ +export var DeploymentState; +(function (DeploymentState) { + /** The deployment is currently re/deploying */ + DeploymentState["Deploying"] = "deploying"; + /** Container is running */ + DeploymentState["Running"] = "running"; + /** Container is created but not running */ + DeploymentState["Created"] = "created"; + /** Container is in restart loop */ + DeploymentState["Restarting"] = "restarting"; + /** Container is being removed */ + DeploymentState["Removing"] = "removing"; + /** Container is paused */ + DeploymentState["Paused"] = "paused"; + /** Container is exited */ + DeploymentState["Exited"] = "exited"; + /** Container is dead */ + DeploymentState["Dead"] = "dead"; + /** The deployment is not deployed (no matching container) */ + DeploymentState["NotDeployed"] = "not_deployed"; + /** Server not reachable for status */ + DeploymentState["Unknown"] = "unknown"; +})(DeploymentState || (DeploymentState = {})); +/** Severity level of problem. */ +export var SeverityLevel; +(function (SeverityLevel) { + /** No problem. */ + SeverityLevel["Ok"] = "OK"; + /** Problem is imminent. */ + SeverityLevel["Warning"] = "WARNING"; + /** Problem fully realized. */ + SeverityLevel["Critical"] = "CRITICAL"; +})(SeverityLevel || (SeverityLevel = {})); +export var Timelength; +(function (Timelength) { + Timelength["OneSecond"] = "1-sec"; + Timelength["FiveSeconds"] = "5-sec"; + Timelength["TenSeconds"] = "10-sec"; + Timelength["FifteenSeconds"] = "15-sec"; + Timelength["ThirtySeconds"] = "30-sec"; + Timelength["OneMinute"] = "1-min"; + Timelength["TwoMinutes"] = "2-min"; + Timelength["FiveMinutes"] = "5-min"; + Timelength["TenMinutes"] = "10-min"; + Timelength["FifteenMinutes"] = "15-min"; + Timelength["ThirtyMinutes"] = "30-min"; + Timelength["OneHour"] = "1-hr"; + Timelength["TwoHours"] = "2-hr"; + Timelength["SixHours"] = "6-hr"; + Timelength["EightHours"] = "8-hr"; + Timelength["TwelveHours"] = "12-hr"; + Timelength["OneDay"] = "1-day"; + Timelength["ThreeDay"] = "3-day"; + Timelength["OneWeek"] = "1-wk"; + Timelength["TwoWeeks"] = "2-wk"; + Timelength["ThirtyDays"] = "30-day"; +})(Timelength || (Timelength = {})); +export var TagColor; +(function (TagColor) { + TagColor["LightSlate"] = "LightSlate"; + TagColor["Slate"] = "Slate"; + TagColor["DarkSlate"] = "DarkSlate"; + TagColor["LightRed"] = "LightRed"; + TagColor["Red"] = "Red"; + TagColor["DarkRed"] = "DarkRed"; + TagColor["LightOrange"] = "LightOrange"; + TagColor["Orange"] = "Orange"; + TagColor["DarkOrange"] = "DarkOrange"; + TagColor["LightAmber"] = "LightAmber"; + TagColor["Amber"] = "Amber"; + TagColor["DarkAmber"] = "DarkAmber"; + TagColor["LightYellow"] = "LightYellow"; + TagColor["Yellow"] = "Yellow"; + TagColor["DarkYellow"] = "DarkYellow"; + TagColor["LightLime"] = "LightLime"; + TagColor["Lime"] = "Lime"; + TagColor["DarkLime"] = "DarkLime"; + TagColor["LightGreen"] = "LightGreen"; + TagColor["Green"] = "Green"; + TagColor["DarkGreen"] = "DarkGreen"; + TagColor["LightEmerald"] = "LightEmerald"; + TagColor["Emerald"] = "Emerald"; + TagColor["DarkEmerald"] = "DarkEmerald"; + TagColor["LightTeal"] = "LightTeal"; + TagColor["Teal"] = "Teal"; + TagColor["DarkTeal"] = "DarkTeal"; + TagColor["LightCyan"] = "LightCyan"; + TagColor["Cyan"] = "Cyan"; + TagColor["DarkCyan"] = "DarkCyan"; + TagColor["LightSky"] = "LightSky"; + TagColor["Sky"] = "Sky"; + TagColor["DarkSky"] = "DarkSky"; + TagColor["LightBlue"] = "LightBlue"; + TagColor["Blue"] = "Blue"; + TagColor["DarkBlue"] = "DarkBlue"; + TagColor["LightIndigo"] = "LightIndigo"; + TagColor["Indigo"] = "Indigo"; + TagColor["DarkIndigo"] = "DarkIndigo"; + TagColor["LightViolet"] = "LightViolet"; + TagColor["Violet"] = "Violet"; + TagColor["DarkViolet"] = "DarkViolet"; + TagColor["LightPurple"] = "LightPurple"; + TagColor["Purple"] = "Purple"; + TagColor["DarkPurple"] = "DarkPurple"; + TagColor["LightFuchsia"] = "LightFuchsia"; + TagColor["Fuchsia"] = "Fuchsia"; + TagColor["DarkFuchsia"] = "DarkFuchsia"; + TagColor["LightPink"] = "LightPink"; + TagColor["Pink"] = "Pink"; + TagColor["DarkPink"] = "DarkPink"; + TagColor["LightRose"] = "LightRose"; + TagColor["Rose"] = "Rose"; + TagColor["DarkRose"] = "DarkRose"; +})(TagColor || (TagColor = {})); export var ContainerStateStatusEnum; (function (ContainerStateStatusEnum) { ContainerStateStatusEnum["Empty"] = ""; @@ -441,6 +452,8 @@ export var ServerState; })(ServerState || (ServerState = {})); export var StackState; (function (StackState) { + /** The stack is currently re/deploying */ + StackState["Deploying"] = "deploying"; /** All containers are running. */ StackState["Running"] = "running"; /** All containers are paused */ @@ -459,7 +472,7 @@ export var StackState; StackState["Unhealthy"] = "unhealthy"; /** The stack is not deployed */ StackState["Down"] = "down"; - /** Server not reachable */ + /** Server not reachable for status */ StackState["Unknown"] = "unknown"; })(StackState || (StackState = {})); export var RepoWebhookAction; @@ -527,9 +540,9 @@ export var SpecificPermission; SpecificPermission["Attach"] = "Attach"; /** * On **Server** - * - Access the `docker inspect` apis + * - Access the `container inspect` apis * On **Stack / Deployment** - * - Access `docker inspect $container` for associated containers + * - Access `container inspect` apis for associated containers */ SpecificPermission["Inspect"] = "Inspect"; /** diff --git a/frontend/src/components/resources/build/index.tsx b/frontend/src/components/resources/build/index.tsx index bde977a72..700f77b66 100644 --- a/frontend/src/components/resources/build/index.tsx +++ b/frontend/src/components/resources/build/index.tsx @@ -10,7 +10,12 @@ import { RequiredResourceComponents } from "@types"; import { Factory, FolderGit, Hammer, Loader2, RefreshCcw } from "lucide-react"; import { BuildConfig } from "./config"; import { BuildTable } from "./table"; -import { DeleteResource, NewResource, ResourceLink } from "../common"; +import { + DeleteResource, + NewResource, + ResourceLink, + StandardSource, +} from "../common"; import { DeploymentTable } from "../deployment/table"; import { RunBuild } from "./actions"; import { @@ -158,6 +163,34 @@ export const BuildComponents: RequiredResourceComponents = { return ; }, + Info: { + Builder: ({ id }) => { + const info = useBuild(id)?.info; + const builder = useBuilder(info?.builder_id); + return builder?.id ? ( + + ) : ( +
+ +
Unknown Builder
+
+ ); + }, + Source: ({ id }) => { + const info = useBuild(id)?.info; + return ; + }, + Branch: ({ id }) => { + const branch = useBuild(id)?.info.branch; + return ( +
+ + {branch} +
+ ); + }, + }, + Status: { Hash: ({ id }) => { const info = useFullBuild(id)?.info; @@ -242,39 +275,6 @@ export const BuildComponents: RequiredResourceComponents = { }, }, - Info: { - Builder: ({ id }) => { - const info = useBuild(id)?.info; - const builder = useBuilder(info?.builder_id); - return builder?.id ? ( - - ) : ( -
- -
Unknown Builder
-
- ); - }, - Repo: ({ id }) => { - const repo = useBuild(id)?.info.repo; - return ( -
- - {repo} -
- ); - }, - Branch: ({ id }) => { - const branch = useBuild(id)?.info.branch; - return ( -
- - {branch} -
- ); - }, - }, - Actions: { RunBuild }, Page: {}, diff --git a/frontend/src/components/resources/build/table.tsx b/frontend/src/components/resources/build/table.tsx index 16195b49c..2b87f202b 100644 --- a/frontend/src/components/resources/build/table.tsx +++ b/frontend/src/components/resources/build/table.tsx @@ -1,7 +1,7 @@ import { TableTags } from "@components/tags"; import { DataTable, SortableHeader } from "@ui/data-table"; import { fmt_version } from "@lib/formatting"; -import { ResourceLink } from "../common"; +import { ResourceLink, StandardSource } from "../common"; import { BuildComponents } from "."; import { Types } from "komodo_client"; import { useSelectedResources } from "@lib/hooks"; @@ -19,27 +19,19 @@ export const BuildTable = ({ builds }: { builds: Types.BuildListItem[] }) => { }} columns={[ { - accessorKey: "name", header: ({ column }) => ( ), + accessorKey: "name", cell: ({ row }) => , size: 200, }, { - // header: ({ column }) => ( - // - // ), - header: "Source", - accessorFn: ({ info: { files_on_host, repo } }) => { - if (files_on_host) { - return "Files on Server"; - } else if (repo) { - return repo; - } else { - return "UI Defined"; - } - }, + header: ({ column }) => ( + + ), + accessorKey: "info.repo", + cell: ({ row }) => , size: 200, }, { diff --git a/frontend/src/components/resources/builder/index.tsx b/frontend/src/components/resources/builder/index.tsx index 23f719ffd..afc367f32 100644 --- a/frontend/src/components/resources/builder/index.tsx +++ b/frontend/src/components/resources/builder/index.tsx @@ -13,19 +13,53 @@ import { SelectValue, } from "@ui/select"; import { Cloud, Bot, Factory } from "lucide-react"; -import { useState } from "react"; +import { ReactNode, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { BuilderConfig } from "./config"; import { DeleteResource, ResourceLink } from "../common"; import { BuilderTable } from "./table"; import { ResourcePageHeader } from "@components/util"; import { GroupActions } from "@components/group-actions"; +import { useServer } from "../server"; +import { cn } from "@lib/utils"; +import { + ColorIntention, + server_state_intention, + stroke_color_class_by_intention, +} from "@lib/color"; export const useBuilder = (id?: string) => useRead("ListBuilders", {}, { refetchInterval: 10_000 }).data?.find( (d) => d.id === id ); +const Icon = ({ id, size }: { id?: string; size: number }) => { + const info = useBuilder(id)?.info; + if (info?.builder_type === "Server" && info.instance_type) { + return ; + } else { + return ; + } +}; + +const ServerIcon = ({ + server_id, + size, +}: { + server_id: string; + size: number; +}) => { + const state = useServer(server_id)?.info.state; + return ( + + ); +}; + export const BuilderInstanceType = ({ id }: { id: string }) => { let info = useBuilder(id)?.info; if (info?.builder_type === "Server") { @@ -35,7 +69,12 @@ export const BuilderInstanceType = ({ id }: { id: string }) => { ) ); } else { - return <>{info?.instance_type}; + return ( +
+ + {info?.instance_type} +
+ ); } }; @@ -120,25 +159,23 @@ export const BuilderComponents: RequiredResourceComponents = { ), - Icon: () => , - BigIcon: () => , + Icon: ({ id }) => , + BigIcon: ({ id }) => , State: () => null, Status: {}, Info: { - Provider: ({ id }) => ( -
- - {useBuilder(id)?.info.builder_type} -
- ), - InstanceType: ({ id }) => ( -
- - -
- ), + Provider: ({ id }) => { + const builder_type = useBuilder(id)?.info.builder_type; + return ( +
+ + {builder_type} +
+ ); + }, + InstanceType: ({ id }) => , }, Actions: {}, @@ -151,17 +188,67 @@ export const BuilderComponents: RequiredResourceComponents = { ResourcePageHeader: ({ id }) => { const builder = useBuilder(id); - + if (builder?.info.builder_type === "Server" && builder.info.instance_type) { + return ( + + ); + } return ( - } - type="Builder" + } /> ); }, }; + +const ServerInnerResourcePageHeader = ({ + builder, + server_id, +}: { + builder: Types.BuilderListItem; + server_id: string; +}) => { + const state = useServer(server_id)?.info.state; + return ( + } + /> + ); +}; + +const InnerResourcePageHeader = ({ + id, + builder, + intent, + icon, +}: { + id: string; + builder: Types.BuilderListItem | undefined; + intent: ColorIntention; + icon: ReactNode; +}) => { + return ( + + ); +}; diff --git a/frontend/src/components/resources/common.tsx b/frontend/src/components/resources/common.tsx index 689238d8a..43c1018dc 100644 --- a/frontend/src/components/resources/common.tsx +++ b/frontend/src/components/resources/common.tsx @@ -2,6 +2,7 @@ import { ActionWithDialog, ConfirmButton, CopyButton, + RepoLink, TextUpdateMenuSimple, } from "@components/util"; import { @@ -29,7 +30,16 @@ import { DialogTrigger, } from "@ui/dialog"; import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; -import { Check, ChevronsUpDown, Copy, SearchX, Trash } from "lucide-react"; +import { + Check, + ChevronsUpDown, + Copy, + Loader2, + NotepadText, + SearchX, + Server, + Trash, +} from "lucide-react"; import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { ResourceComponents } from "."; @@ -353,6 +363,7 @@ export const DeleteResource = ({ }} disabled={isPending} loading={isPending} + forceConfirmDialog /> ); @@ -374,3 +385,36 @@ export const CopyWebhook = ({ ); }; + +export const StandardSource = ({ + info, +}: { + info: + | { + files_on_host: boolean; + repo: string; + repo_link: string; + } + | undefined; +}) => { + if (!info) { + return ; + } + if (info.files_on_host) { + return ( +
+ + Files on Server +
+ ); + } + if (info.repo) { + return ; + } + return ( +
+ + UI Defined +
+ ); +}; diff --git a/frontend/src/components/resources/deployment/config/components/image.tsx b/frontend/src/components/resources/deployment/config/components/image.tsx index efcd775f6..e067c9fc6 100644 --- a/frontend/src/components/resources/deployment/config/components/image.tsx +++ b/frontend/src/components/resources/deployment/config/components/image.tsx @@ -169,6 +169,7 @@ export const ImageConfig = ({ }, }) } + disabled={disabled} /> , - }, - Info: { + Server: ({ id }) => { + const info = useDeployment(id)?.info; + const server = useServer(info?.server_id); + return server?.id ? ( + + ) : ( +
+ +
Unknown Server
+
+ ); + }, Image: ({ id }) => { const config = useFullDeployment(id)?.config; const info = useDeployment(id)?.info; @@ -271,18 +279,6 @@ export const DeploymentComponents: RequiredResourceComponents = { ); }, - Server: ({ id }) => { - const info = useDeployment(id)?.info; - const server = useServer(info?.server_id); - return server?.id ? ( - - ) : ( -
- -
Unknown Server
-
- ); - }, Container: ({ id }) => { const deployment = useDeployment(id); if ( @@ -303,6 +299,10 @@ export const DeploymentComponents: RequiredResourceComponents = { }, }, + Status: { + UpdateAvailable: ({ id }) => , + }, + Actions: { RunBuild: ({ id }) => { const build_id = useDeployment(id)?.info.build_id; diff --git a/frontend/src/components/resources/deployment/table.tsx b/frontend/src/components/resources/deployment/table.tsx index 9069eb757..fe20a4b05 100644 --- a/frontend/src/components/resources/deployment/table.tsx +++ b/frontend/src/components/resources/deployment/table.tsx @@ -63,8 +63,8 @@ export const DeploymentTable = ({ const sb = serverName(b.original.info.server_id); if (!sa && !sb) return 0; - if (!sa) return -1; - if (!sb) return 1; + if (!sa) return 1; + if (!sb) return -1; if (sa > sb) return 1; else if (sa < sb) return -1; diff --git a/frontend/src/components/resources/repo/index.tsx b/frontend/src/components/resources/repo/index.tsx index 50b34f83d..6b10d0360 100644 --- a/frontend/src/components/resources/repo/index.tsx +++ b/frontend/src/components/resources/repo/index.tsx @@ -1,7 +1,7 @@ import { useInvalidate, useRead, useWrite } from "@lib/hooks"; import { RequiredResourceComponents } from "@types"; import { Card } from "@ui/card"; -import { FolderGit, GitBranch, Loader2, RefreshCcw } from "lucide-react"; +import { GitBranch, Loader2, RefreshCcw } from "lucide-react"; import { RepoConfig } from "./config"; import { BuildRepo, CloneRepo, PullRepo } from "./actions"; import { DeleteResource, NewResource, ResourceLink } from "../common"; @@ -14,7 +14,7 @@ import { cn } from "@lib/utils"; import { useServer } from "../server"; import { Types } from "komodo_client"; import { DashboardPieChart } from "@pages/home/dashboard"; -import { ResourcePageHeader, StatusBadge } from "@components/util"; +import { RepoLink, ResourcePageHeader, StatusBadge } from "@components/util"; import { Badge } from "@ui/badge"; import { useToast } from "@ui/use-toast"; import { Button } from "@ui/button"; @@ -89,6 +89,43 @@ export const RepoComponents: RequiredResourceComponents = { return ; }, + Info: { + Target: ({ id }) => { + const info = useRepo(id)?.info; + const server = useServer(info?.server_id); + const builder = useBuilder(info?.builder_id); + return ( +
+ {server?.id && + (builder?.id ? ( +
+ +
+ ) : ( + + ))} + {builder?.id && } +
+ ); + }, + Source: ({ id }) => { + const info = useRepo(id)?.info; + if (!info) { + return ; + } + return ; + }, + Branch: ({ id }) => { + const branch = useRepo(id)?.info.branch; + return ( +
+ + {branch} +
+ ); + }, + }, + Status: { Cloned: ({ id }) => { const info = useRepo(id)?.info; @@ -199,45 +236,6 @@ export const RepoComponents: RequiredResourceComponents = { }, }, - Info: { - Target: ({ id }) => { - const info = useRepo(id)?.info; - const server = useServer(info?.server_id); - const builder = useBuilder(info?.builder_id); - return ( -
- {server?.id && - (builder?.id ? ( -
- -
- ) : ( - - ))} - {builder?.id && } -
- ); - }, - Repo: ({ id }) => { - const repo = useRepo(id)?.info.repo; - return ( -
- - {repo} -
- ); - }, - Branch: ({ id }) => { - const branch = useRepo(id)?.info.branch; - return ( -
- - {branch} -
- ); - }, - }, - Actions: { BuildRepo, PullRepo, CloneRepo }, Page: {}, diff --git a/frontend/src/components/resources/repo/table.tsx b/frontend/src/components/resources/repo/table.tsx index c862a3dd8..a76ebb4cd 100644 --- a/frontend/src/components/resources/repo/table.tsx +++ b/frontend/src/components/resources/repo/table.tsx @@ -4,6 +4,7 @@ import { TableTags } from "@components/tags"; import { RepoComponents } from "."; import { Types } from "komodo_client"; import { useSelectedResources } from "@lib/hooks"; +import { RepoLink } from "@components/util"; export const RepoTable = ({ repos }: { repos: Types.RepoListItem[] }) => { const [_, setSelectedResources] = useSelectedResources("Repo"); @@ -18,32 +19,38 @@ export const RepoTable = ({ repos }: { repos: Types.RepoListItem[] }) => { }} columns={[ { - accessorKey: "name", header: ({ column }) => ( ), + accessorKey: "name", cell: ({ row }) => , size: 200, }, { - accessorKey: "info.repo", header: ({ column }) => ( ), + accessorKey: "info.repo", + cell: ({ row }) => ( + + ), size: 200, }, { - accessorKey: "info.branch", header: ({ column }) => ( ), + accessorKey: "info.branch", size: 200, }, { - accessorKey: "info.state", header: ({ column }) => ( ), + accessorKey: "info.state", cell: ({ row }) => , size: 120, }, diff --git a/frontend/src/components/resources/resource-sync/index.tsx b/frontend/src/components/resources/resource-sync/index.tsx index 807ac2123..7ce2248b9 100644 --- a/frontend/src/components/resources/resource-sync/index.tsx +++ b/frontend/src/components/resources/resource-sync/index.tsx @@ -2,7 +2,7 @@ import { atomWithStorage, useLocalStorage, useRead, useUser } from "@lib/hooks"; import { RequiredResourceComponents } from "@types"; import { Card } from "@ui/card"; import { Clock, FolderSync } from "lucide-react"; -import { DeleteResource, NewResource } from "../common"; +import { DeleteResource, NewResource, StandardSource } from "../common"; import { ResourceSyncTable } from "./table"; import { Types } from "komodo_client"; import { CommitSync, ExecuteSync, RefreshSync } from "./actions"; @@ -174,6 +174,22 @@ export const ResourceSyncComponents: RequiredResourceComponents = { ); }, + Info: { + Source: ({ id }) => { + const info = useResourceSync(id)?.info; + return ; + }, + LastSync: ({ id }) => { + const last_ts = useResourceSync(id)?.info.last_sync_ts; + return ( +
+ + {last_ts ? fmt_date(new Date(last_ts)) : "Never"} +
+ ); + }, + }, + Status: { Hash: ({ id }) => { const info = useFullResourceSync(id)?.info; @@ -232,18 +248,6 @@ export const ResourceSyncComponents: RequiredResourceComponents = { }, }, - Info: { - LastSync: ({ id }) => { - const last_ts = useResourceSync(id)?.info.last_sync_ts; - return ( -
- - {last_ts ? fmt_date(new Date(last_ts)) : "Never"} -
- ); - }, - }, - Actions: { RefreshSync, ExecuteSync, CommitSync }, Page: {}, diff --git a/frontend/src/components/resources/resource-sync/table.tsx b/frontend/src/components/resources/resource-sync/table.tsx index d83ea605a..6dcf456fb 100644 --- a/frontend/src/components/resources/resource-sync/table.tsx +++ b/frontend/src/components/resources/resource-sync/table.tsx @@ -1,5 +1,5 @@ import { DataTable, SortableHeader } from "@ui/data-table"; -import { ResourceLink } from "../common"; +import { ResourceLink, StandardSource } from "../common"; import { TableTags } from "@components/tags"; import { Types } from "komodo_client"; import { ResourceSyncComponents } from "."; @@ -21,34 +21,35 @@ export const ResourceSyncTable = ({ }} columns={[ { - accessorKey: "name", header: ({ column }) => ( ), + accessorKey: "name", cell: ({ row }) => ( ), size: 200, }, { - accessorKey: "info.repo", header: ({ column }) => ( ), + accessorKey: "info.repo", + cell: ({ row }) => , size: 200, }, { - accessorKey: "info.branch", header: ({ column }) => ( ), + accessorKey: "info.branch", size: 200, }, { - accessorKey: "info.state", header: ({ column }) => ( ), + accessorKey: "info.state", cell: ({ row }) => ( ), diff --git a/frontend/src/components/resources/server/index.tsx b/frontend/src/components/resources/server/index.tsx index 6e4f65e01..e5c930269 100644 --- a/frontend/src/components/resources/server/index.tsx +++ b/frontend/src/components/resources/server/index.tsx @@ -12,6 +12,8 @@ import { RefreshCcw, Pause, Square, + AlertCircle, + CheckCircle2, } from "lucide-react"; import { Section } from "@components/layouts"; import { Prune } from "./actions"; @@ -40,6 +42,7 @@ import { GroupActions } from "@components/group-actions"; import { ServerTerminals } from "@components/terminal/server"; import { usePermissions } from "@lib/hooks"; import { Card, CardHeader, CardTitle } from "@ui/card"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@ui/tooltip"; export const useServer = (id?: string) => useRead("ListServers", {}, { refetchInterval: 10_000 }).data?.find( @@ -272,17 +275,56 @@ export const ServerComponents: RequiredResourceComponents = { Info: { Version: ({ id }) => { + const core_version = useRead("GetVersion", {}).data?.version; const version = useRead( "GetPeripheryVersion", { server: id }, { refetchInterval: 5000 } ).data?.version; - const _version = - version === undefined || version === "unknown" ? "unknown" : version; + const mismatch = !!version && !!core_version && version !== core_version; return ( -
+ + +
+ {mismatch ? ( + + ) : ( + + )} + {version ?? "Unknown"} +
+
+ + {mismatch ? ( +
+ Periphery version mismatch. + Expected {core_version}. +
+ ) : ( +
+ Periphery and Core version{" "} + match. +
+ )} +
+
+ ); + if (mismatch) { + } + return ( +
- {_version} + {version ?? "Unknown"}
); }, diff --git a/frontend/src/components/resources/server/table.tsx b/frontend/src/components/resources/server/table.tsx index 3e115305e..0bc3f0905 100644 --- a/frontend/src/components/resources/server/table.tsx +++ b/frontend/src/components/resources/server/table.tsx @@ -51,8 +51,8 @@ export const ServerTable = ({ const sb = resourcesCount(b.original.id); if (!sa && !sb) return 0; - if (!sa) return -1; - if (!sb) return 1; + if (!sa) return 1; + if (!sb) return -1; if (sa > sb) return 1; else if (sa < sb) return -1; diff --git a/frontend/src/components/resources/stack/index.tsx b/frontend/src/components/resources/stack/index.tsx index 1f531f5e0..51230c71e 100644 --- a/frontend/src/components/resources/stack/index.tsx +++ b/frontend/src/components/resources/stack/index.tsx @@ -9,14 +9,17 @@ import { RequiredResourceComponents } from "@types"; import { Card } from "@ui/card"; import { CircleArrowUp, - FolderGit, Layers, Loader2, - NotepadText, RefreshCcw, Server, } from "lucide-react"; -import { DeleteResource, NewResource, ResourceLink } from "../common"; +import { + DeleteResource, + NewResource, + ResourceLink, + StandardSource, +} from "../common"; import { StackTable } from "./table"; import { border_color_class_by_intention, @@ -207,6 +210,45 @@ export const StackComponents: RequiredResourceComponents = { return ; }, + Info: { + Server: ({ id }) => { + const info = useStack(id)?.info; + const server = useServer(info?.server_id); + return server?.id ? ( + + ) : ( +
+ +
Unknown Server
+
+ ); + }, + Source: ({ id }) => { + const info = useStack(id)?.info; + return ; + }, + // Branch: ({ id }) => { + // const config = useFullStack(id)?.config; + // const file_contents = config?.file_contents; + // if (file_contents || !config?.branch) return null; + // return ( + //
+ // + // {config.branch} + //
+ // ); + // }, + Services: ({ id }) => { + const info = useStack(id)?.info; + return ( +
+
{info?.services.length}
+
Service{(info?.services.length ?? 0 > 1) ? "s" : ""}
+
+ ); + }, + }, + Status: { NoConfig: ({ id }) => { const config = useFullStack(id)?.config; @@ -385,50 +427,6 @@ export const StackComponents: RequiredResourceComponents = { }, }, - Info: { - Contents: ({ id }) => { - const config = useFullStack(id)?.config; - const file_contents = config?.file_contents; - if (file_contents) { - return ( -
- - Local -
- ); - } - return ( -
- - {config?.repo} -
- ); - }, - // Branch: ({ id }) => { - // const config = useFullStack(id)?.config; - // const file_contents = config?.file_contents; - // if (file_contents || !config?.branch) return null - // return ( - //
- // - // {config.branch} - //
- // ); - // }, - Server: ({ id }) => { - const info = useStack(id)?.info; - const server = useServer(info?.server_id); - return server?.id ? ( - - ) : ( -
- -
Unknown Server
-
- ); - }, - }, - Actions: { DeployStack, PullStack, diff --git a/frontend/src/components/resources/stack/table.tsx b/frontend/src/components/resources/stack/table.tsx index bec69b601..7af09ceba 100644 --- a/frontend/src/components/resources/stack/table.tsx +++ b/frontend/src/components/resources/stack/table.tsx @@ -1,6 +1,6 @@ import { useRead, useSelectedResources } from "@lib/hooks"; import { DataTable, SortableHeader } from "@ui/data-table"; -import { ResourceLink } from "../common"; +import { ResourceLink, StandardSource } from "../common"; import { TableTags } from "@components/tags"; import { StackComponents, UpdateAvailable } from "."; import { Types } from "komodo_client"; @@ -25,10 +25,10 @@ export const StackTable = ({ stacks }: { stacks: Types.StackListItem[] }) => { }} columns={[ { - accessorKey: "name", header: ({ column }) => ( ), + accessorKey: "name", cell: ({ row }) => { return (
@@ -40,27 +40,35 @@ export const StackTable = ({ stacks }: { stacks: Types.StackListItem[] }) => { size: 200, }, { + header: ({ column }) => ( + + ), accessorKey: "info.server_id", sortingFn: (a, b) => { const sa = serverName(a.original.info.server_id); const sb = serverName(b.original.info.server_id); if (!sa && !sb) return 0; - if (!sa) return -1; - if (!sb) return 1; + if (!sa) return 1; + if (!sb) return -1; if (sa > sb) return 1; else if (sa < sb) return -1; else return 0; }, - header: ({ column }) => ( - - ), cell: ({ row }) => ( ), size: 200, }, + { + header: ({ column }) => ( + + ), + accessorKey: "info.repo", + cell: ({ row }) => , + size: 200, + }, { accessorKey: "info.state", header: ({ column }) => ( diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index 04027e379..07cc94fbf 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -1,10 +1,11 @@ -import { RESOURCE_TARGETS, cn, usableResourcePath } from "@lib/utils"; +import { SIDEBAR_RESOURCES, cn, usableResourcePath } from "@lib/utils"; import { Button } from "@ui/button"; import { AlertTriangle, Bell, Box, Boxes, + CalendarDays, LayoutDashboard, Settings, } from "lucide-react"; @@ -43,7 +44,7 @@ export const Sidebar = () => {

Resources

- {RESOURCE_TARGETS.map((type) => { + {SIDEBAR_RESOURCES.map((type) => { const RTIcon = ResourceComponents[type].Icon; const name = type === "ResourceSync" ? "Sync" : type; return ( @@ -55,6 +56,7 @@ export const Sidebar = () => { /> ); })} +

Notifications

@@ -68,8 +70,15 @@ export const Sidebar = () => { to="/updates" icon={} /> + + } + /> + { ? [, "Containers"] : location.pathname === "/settings" ? [, "Settings"] - : location.pathname === "/alerts" - ? [, "Alerts"] - : location.pathname === "/updates" - ? [, "Updates"] - : location.pathname.split("/")[1] === "user-groups" - ? [, "User Groups"] - : location.pathname.split("/")[1] === "users" - ? [, "Users"] - : [, "Unknown"]; + : location.pathname === "/schedules" + ? [, "Schedules"] + : location.pathname === "/alerts" + ? [, "Alerts"] + : location.pathname === "/updates" + ? [, "Updates"] + : location.pathname.split("/")[1] === "user-groups" + ? [, "User Groups"] + : location.pathname.split("/")[1] === "users" + ? [, "Users"] + : [, "Unknown"]; return ( @@ -202,6 +205,12 @@ const MobileDropdown = () => { + } + to="/schedules" + /> + } diff --git a/frontend/src/components/util.tsx b/frontend/src/components/util.tsx index 993f5c704..b4b95fc42 100644 --- a/frontend/src/components/util.tsx +++ b/frontend/src/components/util.tsx @@ -18,6 +18,7 @@ import { Copy, Database, Edit2, + FolderGit, HardDrive, Loader2, LogOut, @@ -140,6 +141,7 @@ export const ActionWithDialog = ({ additional, targetClassName, variant, + forceConfirmDialog, }: { name: string; title: string; @@ -158,13 +160,18 @@ export const ActionWithDialog = ({ | "ghost" | null | undefined; + /** + * For some ops (Delete), force confirm dialog + * even if disabled. + */ + forceConfirmDialog?: boolean; }) => { const disable_confirm_dialog = useRead("GetCoreInfo", {}).data?.disable_confirm_dialog ?? false; const [open, setOpen] = useState(false); const [input, setInput] = useState(""); - if (disable_confirm_dialog) { + if (!forceConfirmDialog && disable_confirm_dialog) { return ( {
); }; + +export const RepoLink = ({ repo, link }: { repo: string; link: string }) => { + return ( + +
+ + {repo} +
+
+ ); +}; diff --git a/frontend/src/lib/color.ts b/frontend/src/lib/color.ts index a4e6288f7..f83511ad8 100644 --- a/frontend/src/lib/color.ts +++ b/frontend/src/lib/color.ts @@ -130,9 +130,9 @@ export const soft_text_color_class_by_intention = ( }; export const server_state_intention: ( - status?: Types.ServerState -) => ColorIntention = (status) => { - switch (status) { + state?: Types.ServerState +) => ColorIntention = (state) => { + switch (state) { case Types.ServerState.Ok: return "Good"; case Types.ServerState.NotOk: @@ -150,6 +150,8 @@ export const deployment_state_intention: ( switch (state) { case undefined: return "None"; + case Types.DeploymentState.Deploying: + return "Warning"; case Types.DeploymentState.Running: return "Good"; case Types.DeploymentState.NotDeployed: @@ -222,6 +224,8 @@ export const stack_state_intention = (state?: Types.StackState) => { switch (state) { case undefined: return "None"; + case Types.StackState.Deploying: + return "Warning"; case Types.StackState.Running: return "Good"; case Types.StackState.Paused: diff --git a/frontend/src/lib/socket.tsx b/frontend/src/lib/socket.tsx index 41814dc52..a3b251122 100644 --- a/frontend/src/lib/socket.tsx +++ b/frontend/src/lib/socket.tsx @@ -278,6 +278,7 @@ const on_update = ( if (update.target.type === "Procedure") { invalidate( + ["ListSchedules"], ["ListProcedures"], ["ListFullProcedures"], ["GetProceduresSummary"], @@ -287,6 +288,7 @@ const on_update = ( if (update.target.type === "Action") { invalidate( + ["ListSchedules"], ["ListActions"], ["ListFullActions"], ["GetActionsSummary"], diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 169875ea9..4263354aa 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -26,6 +26,12 @@ export const RESOURCE_TARGETS: UsableResource[] = [ "ResourceSync", ]; +export const SETTINGS_RESOURCES: UsableResource[] = ["Builder", "Alerter"]; + +export const SIDEBAR_RESOURCES: UsableResource[] = RESOURCE_TARGETS.filter( + (target) => !SETTINGS_RESOURCES.includes(target) +); + export function env_to_text(envVars: Types.EnvironmentVar[] | undefined) { return envVars?.reduce( (prev, { variable, value }) => diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index e091adbd5..1f41784d7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -29,17 +29,18 @@ export const homeViewAtom = atomWithStorage( "Dashboard" ); -init_monaco().then(() => - ReactDOM.createRoot(document.getElementById("root")!).render( - // - - - - - - - - - // - ) +// Don't need to await this to render. +init_monaco(); + +ReactDOM.createRoot(document.getElementById("root")!).render( + // + + + + + + + + + // ); diff --git a/frontend/src/pages/alerts.tsx b/frontend/src/pages/alerts.tsx index 6087ef7af..9b91a8885 100644 --- a/frontend/src/pages/alerts.tsx +++ b/frontend/src/pages/alerts.tsx @@ -43,7 +43,7 @@ const FALLBACK_ALERT_TYPES = [ "AwsBuilderTerminationFailed", ]; -export const AlertsPage = () => { +export default function AlertsPage() { const [page, setPage] = useState(0); const [params, setParams] = useSearchParams(); @@ -211,4 +211,4 @@ export const AlertsPage = () => {
); -}; +} diff --git a/frontend/src/pages/containers.tsx b/frontend/src/pages/containers.tsx index f2d06fa5f..c81888b63 100644 --- a/frontend/src/pages/containers.tsx +++ b/frontend/src/pages/containers.tsx @@ -8,7 +8,7 @@ import { Input } from "@ui/input"; import { Box, Search } from "lucide-react"; import { Fragment, useCallback, useMemo, useState } from "react"; -export const ContainersPage = () => { +export default function ContainersPage() { const [search, setSearch] = useState(""); const searchSplit = search .toLowerCase() @@ -183,4 +183,4 @@ export const ContainersPage = () => { ); -}; +} diff --git a/frontend/src/pages/home/all_resources.tsx b/frontend/src/pages/home/all_resources.tsx index 4c507add3..c030ee15b 100644 --- a/frontend/src/pages/home/all_resources.tsx +++ b/frontend/src/pages/home/all_resources.tsx @@ -17,7 +17,7 @@ import { Input } from "@ui/input"; import { AlertTriangle } from "lucide-react"; import { useState } from "react"; -export const AllResources = () => { +export default function AllResources() { const [search, setSearch] = useState(""); const tags = useTagsFilter(); const noResources = useNoResources(); @@ -63,7 +63,7 @@ export const AllResources = () => { ); -}; +} const TableSection = ({ type, diff --git a/frontend/src/pages/home/dashboard.tsx b/frontend/src/pages/home/dashboard.tsx index 482f541c2..c7bb86a46 100644 --- a/frontend/src/pages/home/dashboard.tsx +++ b/frontend/src/pages/home/dashboard.tsx @@ -24,7 +24,7 @@ import { Link } from "react-router-dom"; import { UpdateAvailable as StackUpdateAvailable } from "@components/resources/stack"; import { UpdateAvailable as DeploymentUpdateAvailable } from "@components/resources/deployment"; -export const Dashboard = () => { +export default function Dashboard() { const noResources = useNoResources(); const user = useUser().data!; return ( @@ -59,7 +59,7 @@ export const Dashboard = () => { ); -}; +} const ResourceRow = ({ type }: { type: UsableResource }) => { const _recents = useUser().data?.recents?.[type]?.slice(0, 6); diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx index f36de2eda..b2c8d398d 100644 --- a/frontend/src/pages/home/index.tsx +++ b/frontend/src/pages/home/index.tsx @@ -1,11 +1,13 @@ import { homeViewAtom } from "@main"; import { useAtom } from "jotai"; -import { Dashboard } from "./dashboard"; -import { AllResources } from "./all_resources"; -import { Tree } from "./tree"; import { useSetTitle } from "@lib/hooks"; +import { lazy } from "react"; -export const Home = () => { +const Dashboard = lazy(() => import("./dashboard")); +const AllResources = lazy(() => import("./all_resources")); +const Tree = lazy(() => import("./tree")); + +export default function Home() { useSetTitle(); const [view] = useAtom(homeViewAtom); switch (view) { @@ -16,4 +18,4 @@ export const Home = () => { case "Tree": return ; } -}; +} diff --git a/frontend/src/pages/home/tree.tsx b/frontend/src/pages/home/tree.tsx index a92c814c3..ea9e73b70 100644 --- a/frontend/src/pages/home/tree.tsx +++ b/frontend/src/pages/home/tree.tsx @@ -14,7 +14,7 @@ import { Link } from "react-router-dom"; const searchAtom = atom(""); -export const Tree = () => { +export default function Tree() { const [search, setSearch] = useAtom(searchAtom); const tags = useTagsFilter(); const servers = useRead("ListServers", { query: { tags } }).data; @@ -37,14 +37,12 @@ export const Tree = () => { >
- {servers?.map((server) => ( - - ))} + {servers?.map((server) => )}
); -}; +} const Server = ({ id }: { id: string }) => { const [search] = useAtom(searchAtom); diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 6ba4218fd..19628b3e0 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -62,7 +62,7 @@ const useExchangeToken = () => { return true; }; -export const Login = () => { +export default function Login() { const options = useLoginOptions().data; const [creds, set] = useState({ username: "", password: "" }); const userInvalidate = useUserInvalidate(); @@ -253,4 +253,4 @@ export const Login = () => { ); -}; +} diff --git a/frontend/src/pages/resource.tsx b/frontend/src/pages/resource.tsx index 30c33a0fa..058686a41 100644 --- a/frontend/src/pages/resource.tsx +++ b/frontend/src/pages/resource.tsx @@ -13,7 +13,7 @@ import { useResourceParamType, useSetTitle, } from "@lib/hooks"; -import { usableResourcePath } from "@lib/utils"; +import { SETTINGS_RESOURCES, usableResourcePath } from "@lib/utils"; import { Types } from "komodo_client"; import { UsableResource } from "@types"; import { Button } from "@ui/button"; @@ -22,14 +22,14 @@ import { Link, useParams } from "react-router-dom"; import { ResourceNotifications } from "./resource-notifications"; import { NotFound } from "@components/util"; -export const Resource = () => { +export default function Resource() { const type = useResourceParamType()!; const id = useParams().id as string; if (!type || !id) return null; return ; -}; +} const ResourceInner = ({ type, id }: { type: UsableResource; id: string }) => { const resources = useRead(`List${type}s`, {}).data; @@ -59,7 +59,14 @@ const ResourceInner = ({ type, id }: { type: UsableResource; id: string }) => { return (
- +
+ {links && links.length > 0 && ( +
+ {links?.map((link) => ( + + +
+ {link} +
+
+ ))} +
+ )}

Tags:

{ +export default function Resources({ _type }: { _type?: UsableResource }) { const is_admin = useUser().data?.admin ?? false; const disable_non_admin_create = useRead("GetCoreInfo", {}).data?.disable_non_admin_create ?? true; - const type = useResourceParamType()!; + const __type = useResourceParamType()!; + const type = _type ? _type : __type; const name = type === "ResourceSync" ? "Resource Sync" : type; useSetTitle(name + "s"); const [search, set] = useState(""); @@ -94,4 +96,4 @@ export const Resources = () => {
); -}; +} diff --git a/frontend/src/pages/schedules.tsx b/frontend/src/pages/schedules.tsx new file mode 100644 index 000000000..76de23a0d --- /dev/null +++ b/frontend/src/pages/schedules.tsx @@ -0,0 +1,146 @@ +import { Page } from "@components/layouts"; +import { ResourceLink } from "@components/resources/common"; +import { TableTags, TagsFilter } from "@components/tags"; +import { + usePermissions, + useRead, + useSetTitle, + useTags, + useWrite, +} from "@lib/hooks"; +import { filterBySplit } from "@lib/utils"; +import { UsableResource } from "@types"; +import { DataTable, SortableHeader } from "@ui/data-table"; +import { Input } from "@ui/input"; +import { Switch } from "@ui/switch"; +import { useToast } from "@ui/use-toast"; +import { CalendarDays, Search } from "lucide-react"; +import { useState } from "react"; + +export default function SchedulesPage() { + useSetTitle("Schedules"); + const [search, set] = useState(""); + const { tags } = useTags(); + const schedules = useRead("ListSchedules", { tags }).data; + const filtered = filterBySplit(schedules ?? [], search, (item) => item.name); + return ( + } + title="Schedules" + subtitle={ +
+ See an overview of your scheduled tasks. +
+ } + > +
+
+
+ +
+ + set(e.target.value)} + placeholder="search..." + className="pl-8 w-[200px] lg:w-[300px]" + /> +
+
+
+ ( + + ), + cell: ({ row }) => ( + + ), + }, + { + size: 200, + accessorKey: "schedule", + header: ({ column }) => ( + + ), + }, + { + size: 200, + accessorKey: "next_scheduled_run", + header: ({ column }) => ( + + ), + sortingFn: (a, b) => { + const sa = a.original.next_scheduled_run; + const sb = b.original.next_scheduled_run; + + if (!sa && !sb) return 0; + if (!sa) return 1; + if (!sb) return -1; + + if (sa > sb) return 1; + else if (sa < sb) return -1; + else return 0; + }, + cell: ({ row }) => + row.original.next_scheduled_run + ? new Date(row.original.next_scheduled_run).toLocaleString() + : "Not Scheduled", + }, + { + size: 100, + accessorKey: "enabled", + header: ({ column }) => ( + + ), + cell: ({ row: { original: schedule } }) => ( + + ), + }, + { + header: "Tags", + cell: ({ row }) => , + }, + ]} + /> +
+
+ ); +} + +const ScheduleEnableSwitch = ({ + type, + id, + enabled, +}: { + type: UsableResource; + id: string; + enabled: boolean; +}) => { + const { canWrite } = usePermissions({ type, id }); + const { toast } = useToast(); + const { mutate } = useWrite(`Update${type}`, { + onSuccess: () => toast({ title: "Updated Schedule enabled." }), + }); + return ( + + mutate({ id, config: { schedule_enabled: enabled } }) + } + disabled={!canWrite} + /> + ); +}; diff --git a/frontend/src/pages/server-info/container/index.tsx b/frontend/src/pages/server-info/container/index.tsx index be6e8a4d4..93b2022ae 100644 --- a/frontend/src/pages/server-info/container/index.tsx +++ b/frontend/src/pages/server-info/container/index.tsx @@ -31,7 +31,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; import { ContainerTerminal } from "@components/terminal/container"; import { ContainerInspect } from "./inspect"; -export const ContainerPage = () => { +export default function ContainerPage() { const { type, id, container } = useParams() as { type: string; id: string; @@ -43,7 +43,7 @@ export const ContainerPage = () => { return ( ); -}; +} const ContainerPageInner = ({ id, diff --git a/frontend/src/pages/server-info/image.tsx b/frontend/src/pages/server-info/image.tsx index c1addbd0a..196145a7c 100644 --- a/frontend/src/pages/server-info/image.tsx +++ b/frontend/src/pages/server-info/image.tsx @@ -27,7 +27,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { useState } from "react"; import { MonacoEditor } from "@components/monaco"; -export const ImagePage = () => { +export default function ImagePage() { const { type, id, image } = useParams() as { type: string; id: string; @@ -37,7 +37,7 @@ export const ImagePage = () => { return
This resource type does not have any images.
; } return ; -}; +} const ImagePageInner = ({ id, diff --git a/frontend/src/pages/server-info/network.tsx b/frontend/src/pages/server-info/network.tsx index f4595835a..be590fe5b 100644 --- a/frontend/src/pages/server-info/network.tsx +++ b/frontend/src/pages/server-info/network.tsx @@ -28,7 +28,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { useState } from "react"; import { MonacoEditor } from "@components/monaco"; -export const NetworkPage = () => { +export default function NetworkPage() { const { type, id, network } = useParams() as { type: string; id: string; @@ -38,7 +38,7 @@ export const NetworkPage = () => { return
This resource type does not have any networks.
; } return ; -}; +} const NetworkPageInner = ({ id, diff --git a/frontend/src/pages/server-info/volume.tsx b/frontend/src/pages/server-info/volume.tsx index 792a29172..044f75e9d 100644 --- a/frontend/src/pages/server-info/volume.tsx +++ b/frontend/src/pages/server-info/volume.tsx @@ -20,7 +20,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { useState } from "react"; import { MonacoEditor } from "@components/monaco"; -export const VolumePage = () => { +export default function VolumePage() { const { type, id, volume } = useParams() as { type: string; id: string; @@ -30,7 +30,7 @@ export const VolumePage = () => { return
This resource type does not have any volumes.
; } return ; -}; +} const VolumePageInner = ({ id, diff --git a/frontend/src/pages/settings/index.tsx b/frontend/src/pages/settings/index.tsx index e7b2049dd..20257b963 100644 --- a/frontend/src/pages/settings/index.tsx +++ b/frontend/src/pages/settings/index.tsx @@ -1,13 +1,14 @@ +import { useAtom } from "jotai"; import { atomWithStorage, useUser } from "@lib/hooks"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs"; +import { Page } from "@components/layouts"; +import { ExportButton } from "@components/export"; +import Resources from "@pages/resources"; import { Variables } from "./variables"; import { Tags } from "./tags"; import { UsersPage } from "./users"; import { Profile } from "./profile"; -import { Page } from "@components/layouts"; import { ProvidersPage } from "./providers"; -import { ExportButton } from "@components/export"; -import { useAtom } from "jotai"; type SettingsView = "Variables" | "Tags" | "Providers" | "Users" | "Profile"; @@ -15,7 +16,7 @@ const viewAtom = atomWithStorage("settings-view-v2", "Variables"); export const useSettingsView = () => useAtom(viewAtom); -export const Settings = () => { +export default function Settings() { const user = useUser().data; const [view, setView] = useSettingsView(); const currentView = @@ -33,6 +34,8 @@ export const Settings = () => { Variables Tags + Builders + Alerters {user?.admin && ( Providers )} @@ -49,6 +52,12 @@ export const Settings = () => { + + + + + + {user?.admin && ( @@ -65,4 +74,4 @@ export const Settings = () => { ); -}; +} diff --git a/frontend/src/pages/stack-service/index.tsx b/frontend/src/pages/stack-service/index.tsx index ef25ae82c..41c60a064 100644 --- a/frontend/src/pages/stack-service/index.tsx +++ b/frontend/src/pages/stack-service/index.tsx @@ -48,7 +48,7 @@ const Actions: { [action: string]: IdServiceComponent } = { DestroyStack, }; -export const StackServicePage = () => { +export default function StackServicePage() { const { type, id, service } = useParams() as { type: string; id: string; @@ -58,7 +58,7 @@ export const StackServicePage = () => { return
This resource type does not have any services.
; } return ; -}; +} const StackServicePageInner = ({ stack_id, diff --git a/frontend/src/pages/updates.tsx b/frontend/src/pages/updates.tsx index 4dc962ed8..a619fab1a 100644 --- a/frontend/src/pages/updates.tsx +++ b/frontend/src/pages/updates.tsx @@ -36,7 +36,7 @@ import { } from "@ui/select"; import { ResourceSelector } from "@components/resources/common"; -export const UpdatesPage = () => { +export default function UpdatesPage() { const [page, setPage] = useState(0); const [params, setParams] = useSearchParams(); @@ -168,7 +168,7 @@ export const UpdatesPage = () => {
); -}; +} export const Updates = () => { const type = useResourceParamType()!; diff --git a/frontend/src/pages/user-group.tsx b/frontend/src/pages/user-group.tsx index c9b9cc113..bb88b2236 100644 --- a/frontend/src/pages/user-group.tsx +++ b/frontend/src/pages/user-group.tsx @@ -23,7 +23,7 @@ import { useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Switch } from "@ui/switch"; -export const UserGroupPage = () => { +export default function UserGroupPage() { const { toast } = useToast(); const inv = useInvalidate(); const group_id = useParams().id as string; @@ -143,7 +143,7 @@ export const UserGroupPage = () => { ); -}; +} const AddUserToGroup = ({ group_id }: { group_id: string }) => { const inv = useInvalidate(); diff --git a/frontend/src/pages/user.tsx b/frontend/src/pages/user.tsx index 7989d3696..371716191 100644 --- a/frontend/src/pages/user.tsx +++ b/frontend/src/pages/user.tsx @@ -15,7 +15,7 @@ import { Link, useParams } from "react-router-dom"; import { Button } from "@ui/button"; import { Card, CardContent, CardHeader } from "@ui/card"; -export const UserPage = () => { +export default function UserPage() { const admin_user = useUser().data; const { toast } = useToast(); const inv = useInvalidate(); @@ -128,7 +128,7 @@ export const UserPage = () => { )} ); -}; +} const ApiKeysTable = ({ user_id }: { user_id: string }) => { const keys = useRead("ListApiKeysForServiceUser", { user: user_id }).data; diff --git a/frontend/src/pages/user_disabled.tsx b/frontend/src/pages/user_disabled.tsx index e0e94f0a4..4eb979e76 100644 --- a/frontend/src/pages/user_disabled.tsx +++ b/frontend/src/pages/user_disabled.tsx @@ -2,7 +2,7 @@ import { AUTH_TOKEN_STORAGE_KEY } from "@main"; import { Button } from "@ui/button"; import { UserX } from "lucide-react"; -export const UserDisabled = () => { +export default function UserDisabled() { return (
@@ -20,4 +20,4 @@ export const UserDisabled = () => {
); -}; +} diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 3455ba1c5..f35b59df7 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -1,73 +1,29 @@ import { Layout } from "@components/layouts"; import { useUser } from "@lib/hooks"; -import { Login } from "@pages/login"; -import { Resource } from "@pages/resource"; -import { Resources } from "@pages/resources"; -import { RouterProvider, createBrowserRouter } from "react-router-dom"; -import { Tree } from "@pages/home/tree"; -import { UpdatesPage } from "@pages/updates"; -import { AllResources } from "@pages/home/all_resources"; -import { UserDisabled } from "@pages/user_disabled"; -import { Home } from "@pages/home"; -import { AlertsPage } from "@pages/alerts"; -import { UserPage } from "@pages/user"; -import { UserGroupPage } from "@pages/user-group"; -import { Settings } from "@pages/settings"; -import { StackServicePage } from "@pages/stack-service"; -import { NetworkPage } from "@pages/server-info/network"; -import { ImagePage } from "@pages/server-info/image"; -import { VolumePage } from "@pages/server-info/volume"; -import { ContainerPage } from "@pages/server-info/container"; -import { ContainersPage } from "@pages/containers"; +import { Loader2 } from "lucide-react"; +import { lazy, Suspense } from "react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; -const ROUTER = createBrowserRouter([ - { - path: "/", - element: , - children: [ - { path: "", element: }, - { path: "settings", element: }, - { path: "tree", element: }, - { path: "alerts", element: }, - { path: "updates", element: }, - { path: "updates", element: }, - { path: "containers", element: }, - { path: "resources", element: }, - { path: "user-groups/:id", element: }, - { - path: "users", - children: [{ path: ":id", element: }], - }, - { - path: ":type", - children: [ - { path: "", element: }, - { path: ":id", element: }, - { - path: ":id/service/:service", - element: , - }, - { - path: ":id/container/:container", - element: , - }, - { - path: ":id/network/:network", - element: , - }, - { - path: ":id/image/:image", - element: , - }, - { - path: ":id/volume/:volume", - element: , - }, - ], - }, - ], - }, -]); +// Lazy import pages +const Resources = lazy(() => import("@pages/resources")); +const Resource = lazy(() => import("@pages/resource")); +const Login = lazy(() => import("@pages/login")); +const Tree = lazy(() => import("@pages/home/tree")); +const UpdatesPage = lazy(() => import("@pages/updates")); +const AllResources = lazy(() => import("@pages/home/all_resources")); +const UserDisabled = lazy(() => import("@pages/user_disabled")); +const Home = lazy(() => import("@pages/home")); +const AlertsPage = lazy(() => import("@pages/alerts")); +const UserPage = lazy(() => import("@pages/user")); +const UserGroupPage = lazy(() => import("@pages/user-group")); +const Settings = lazy(() => import("@pages/settings")); +const StackServicePage = lazy(() => import("@pages/stack-service")); +const NetworkPage = lazy(() => import("@pages/server-info/network")); +const ImagePage = lazy(() => import("@pages/server-info/image")); +const VolumePage = lazy(() => import("@pages/server-info/volume")); +const ContainerPage = lazy(() => import("@pages/server-info/container")); +const ContainersPage = lazy(() => import("@pages/containers")); +const SchedulesPage = lazy(() => import("@pages/schedules")); export const Router = () => { const { data: user, isLoading, error } = useUser(); @@ -76,5 +32,47 @@ export const Router = () => { if (!user || error) return ; if (!user.enabled) return ; - return ; + return ( + + + + } + > + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> + } + /> + } + /> + } /> + } /> + } /> + + + + + + ); + + // return ; }; diff --git a/lib/git/src/commit.rs b/lib/git/src/commit.rs index 5d275c828..2b4bd3269 100644 --- a/lib/git/src/commit.rs +++ b/lib/git/src/commit.rs @@ -117,7 +117,7 @@ pub async fn commit_file_inner( let push_log = run_komodo_command( "Push", repo_dir, - format!("git push -f --set-upstream origin {branch}"), + format!("git push --set-upstream origin {branch}"), ) .await; res.logs.push(push_log); @@ -170,7 +170,7 @@ pub async fn commit_all( let push_log = run_komodo_command( "Push", repo_dir, - format!("git push -f --set-upstream origin {branch}"), + format!("git push --set-upstream origin {branch}"), ) .await; res.logs.push(push_log); diff --git a/lib/git/src/pull.rs b/lib/git/src/pull.rs index ee22853de..f0b5296f8 100644 --- a/lib/git/src/pull.rs +++ b/lib/git/src/pull.rs @@ -54,10 +54,11 @@ where T: Into + std::fmt::Debug, { let args: CloneArgs = clone_args.into(); - let folder_path = args.path(repo_dir); + let repo_dir = args.path(repo_dir); + let repo_url = args.remote_url(access_token.as_deref())?; // Acquire the path lock - let lock = pull_cache().get_lock(folder_path.clone()).await; + let lock = pull_cache().get_lock(repo_dir.clone()).await; // Lock the path lock, prevents simultaneous pulls by // ensuring simultaneous pulls will wait for first to finish @@ -73,10 +74,10 @@ where let mut logs = Vec::new(); // Check for '.git' path to see if the folder is initialized as a git repo - let dot_git_path = folder_path.join(".git"); + let dot_git_path = repo_dir.join(".git"); if !dot_git_path.exists() { crate::init::init_folder_as_repo( - &folder_path, + &repo_dir, &args, access_token.as_deref(), &mut logs, @@ -92,12 +93,10 @@ where } } - let repo_url = args.remote_url(access_token.as_deref())?; - // Set remote url let mut set_remote = run_komodo_command( "Set git remote", - folder_path.as_ref(), + repo_dir.as_ref(), format!("git remote set-url origin {repo_url}"), ) .await; @@ -122,7 +121,7 @@ where let checkout = run_komodo_command( "Checkout branch", - folder_path.as_ref(), + repo_dir.as_ref(), format!("git checkout -f {}", args.branch), ) .await; @@ -138,7 +137,7 @@ where let pull_log = run_komodo_command( "Git pull", - folder_path.as_ref(), + repo_dir.as_ref(), format!("git pull --rebase --force origin {}", args.branch), ) .await; @@ -155,7 +154,7 @@ where if let Some(commit) = args.commit { let reset_log = run_komodo_command( "Set commit", - folder_path.as_ref(), + repo_dir.as_ref(), format!("git reset --hard {commit}"), ) .await; @@ -163,7 +162,7 @@ where } let (hash, message) = - match get_commit_hash_log(&folder_path).await { + match get_commit_hash_log(&repo_dir).await { Ok((log, hash, message)) => { logs.push(log); (Some(hash), Some(message)) @@ -184,7 +183,7 @@ where environment, env_file_path, secrets, - &folder_path, + &repo_dir, &mut logs, ) .await diff --git a/runfile.toml b/runfile.toml index 3bfef1e16..43e28f018 100644 --- a/runfile.toml +++ b/runfile.toml @@ -1,3 +1,5 @@ +# Runfile | https://crates.io/crates/runnables-cli + [dev-frontend] description = "starts the frontend in dev mode" path = "frontend"