From 64d13666a9cd5887dd751c02b44e49ddaea25ea8 Mon Sep 17 00:00:00 2001 From: Maxwell Becker <49575486+mbecker20@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:59:52 -0800 Subject: [PATCH] 1.16.10 (#178) * send alert on auto update * scrolling / capturing monaco editors * deployed services has correct image * serde default services for backward compat * improve auto update config --- Cargo.lock | 26 ++-- Cargo.toml | 2 +- bin/core/src/alert/discord.rs | 23 ++++ bin/core/src/alert/slack.rs | 45 +++++++ bin/core/src/api/execute/stack.rs | 25 +--- bin/core/src/monitor/resources.rs | 113 ++++++++++++++---- bin/periphery/src/compose.rs | 73 ++++++++++- client/core/rs/src/entities/alert.rs | 28 +++++ client/core/ts/package.json | 2 +- client/core/ts/src/types.ts | 38 +++++- client/periphery/rs/src/api/compose.rs | 9 +- frontend/public/client/types.d.ts | 44 ++++++- frontend/src/components/config/util.tsx | 38 +++--- frontend/src/components/monaco.tsx | 14 ++- .../resources/alerter/config/alert_types.tsx | 10 +- .../resources/deployment/config/index.tsx | 15 ++- .../resources/resource-sync/config.tsx | 17 ++- .../src/components/resources/stack/config.tsx | 15 ++- frontend/src/lib/hooks.ts | 19 +++ frontend/src/pages/alerts.tsx | 8 +- 20 files changed, 449 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 644fabd34..7f63eba63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ [[package]] name = "alerter" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "axum", @@ -847,7 +847,7 @@ dependencies = [ [[package]] name = "cache" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "tokio", @@ -951,7 +951,7 @@ dependencies = [ [[package]] name = "command" -version = "1.16.9" +version = "1.16.10" dependencies = [ "komodo_client", "run_command", @@ -1363,7 +1363,7 @@ dependencies = [ [[package]] name = "environment_file" -version = "1.16.9" +version = "1.16.10" dependencies = [ "thiserror", ] @@ -1447,7 +1447,7 @@ dependencies = [ [[package]] name = "formatting" -version = "1.16.9" +version = "1.16.10" dependencies = [ "serror", ] @@ -1579,7 +1579,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "cache", @@ -2200,7 +2200,7 @@ dependencies = [ [[package]] name = "komodo_cli" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "clap", @@ -2216,7 +2216,7 @@ dependencies = [ [[package]] name = "komodo_client" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "async_timing_util", @@ -2247,7 +2247,7 @@ dependencies = [ [[package]] name = "komodo_core" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "async_timing_util", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "komodo_periphery" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "async_timing_util", @@ -2395,7 +2395,7 @@ dependencies = [ [[package]] name = "logger" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "komodo_client", @@ -3101,7 +3101,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "periphery_client" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "komodo_client", @@ -4876,7 +4876,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "update_logger" -version = "1.16.9" +version = "1.16.10" dependencies = [ "anyhow", "komodo_client", diff --git a/Cargo.toml b/Cargo.toml index 98ddc2723..99e9bc30d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ ] [workspace.package] -version = "1.16.9" +version = "1.16.10" edition = "2021" authors = ["mbecker20 "] license = "GPL-3.0-or-later" diff --git a/bin/core/src/alert/discord.rs b/bin/core/src/alert/discord.rs index 0949ed964..2bf4c804f 100644 --- a/bin/core/src/alert/discord.rs +++ b/bin/core/src/alert/discord.rs @@ -100,6 +100,16 @@ pub async fn send_alert( let link = resource_link(ResourceTargetVariant::Deployment, id); format!("⬆ Deployment **{name}** has an update available\nserver: **{server_name}**\nimage: **{image}**\n{link}") } + AlertData::DeploymentAutoUpdated { + id, + name, + server_id: _server_id, + server_name, + image, + } => { + let link = resource_link(ResourceTargetVariant::Deployment, id); + format!("⬆ Deployment **{name}** was updated automatically ⏫\nserver: **{server_name}**\nimage: **{image}**\n{link}") + } AlertData::StackStateChange { id, name, @@ -123,6 +133,19 @@ pub async fn send_alert( let link = resource_link(ResourceTargetVariant::Stack, id); format!("⬆ Stack **{name}** has an update available\nserver: **{server_name}**\nservice: **{service}**\nimage: **{image}**\n{link}") } + AlertData::StackAutoUpdated { + id, + name, + server_id: _server_id, + server_name, + images, + } => { + let link = resource_link(ResourceTargetVariant::Deployment, id); + let images_label = + if images.len() > 1 { "images" } else { "image" }; + let images = images.join(", "); + format!("⬆ Stack **{name}** was updated automatically ⏫\nserver: **{server_name}**\n{images_label}: **{images}**\n{link}") + } AlertData::AwsBuilderTerminationFailed { instance_id, message, diff --git a/bin/core/src/alert/slack.rs b/bin/core/src/alert/slack.rs index dcd8f9d6c..69db726fe 100644 --- a/bin/core/src/alert/slack.rs +++ b/bin/core/src/alert/slack.rs @@ -216,6 +216,27 @@ pub async fn send_alert( ]; (text, blocks.into()) } + AlertData::DeploymentAutoUpdated { + id, + name, + server_name, + server_id: _server_id, + image, + } => { + let text = + format!("⬆ Deployment *{name}* was updated automatically ⏫"); + let blocks = vec![ + Block::header(text.clone()), + Block::section(format!( + "server: *{server_name}*\nimage: *{image}*", + )), + Block::section(resource_link( + ResourceTargetVariant::Deployment, + id, + )), + ]; + (text, blocks.into()) + } AlertData::StackStateChange { name, server_name, @@ -259,6 +280,30 @@ pub async fn send_alert( ]; (text, blocks.into()) } + AlertData::StackAutoUpdated { + id, + name, + server_name, + server_id: _server_id, + images, + } => { + let text = + format!("⬆ Stack *{name}* was updated automatically ⏫"); + let images_label = + if images.len() > 1 { "images" } else { "image" }; + let images = images.join(", "); + let blocks = vec![ + Block::header(text.clone()), + Block::section(format!( + "server: *{server_name}*\n{images_label}: *{images}*", + )), + Block::section(resource_link( + ResourceTargetVariant::Stack, + id, + )), + ]; + (text, blocks.into()) + } AlertData::AwsBuilderTerminationFailed { instance_id, message, diff --git a/bin/core/src/api/execute/stack.rs b/bin/core/src/api/execute/stack.rs index 69d2fcd37..d6fd1d20d 100644 --- a/bin/core/src/api/execute/stack.rs +++ b/bin/core/src/api/execute/stack.rs @@ -30,10 +30,7 @@ use crate::{ }, monitor::update_cache_for_server, resource, - stack::{ - execute::execute_compose, get_stack_and_server, - services::extract_services_into_res, - }, + stack::{execute::execute_compose, get_stack_and_server}, state::{action_states, db_client, State}, }; @@ -163,6 +160,7 @@ impl Resolve for State { let ComposeUpResponse { logs, deployed, + services, file_contents, missing_files, remote_errors, @@ -181,24 +179,11 @@ impl Resolve for State { update.logs.extend(logs); let update_info = async { - let latest_services = if !file_contents.is_empty() { - let mut services = Vec::new(); - for contents in &file_contents { - if let Err(e) = extract_services_into_res( - &stack.project_name(true), - &contents.contents, - &mut services, - ) { - update.push_error_log( - "extract services", - format_serror(&e.context(format!("Failed to extract stack services for compose file path {}. Things probably won't work correctly", contents.path)).into()) - ); - } - } - services - } else { + let latest_services = if services.is_empty() { // maybe better to do something else here for services. stack.info.latest_services.clone() + } else { + services }; // This ensures to get the latest project name, diff --git a/bin/core/src/monitor/resources.rs b/bin/core/src/monitor/resources.rs index a991ecc6c..c184f2a86 100644 --- a/bin/core/src/monitor/resources.rs +++ b/bin/core/src/monitor/resources.rs @@ -103,11 +103,12 @@ pub async fn update_deployment_cache( .busy() .unwrap_or(true) { - let deployment = deployment.name.clone(); + let id = deployment.id.clone(); + let server_name = server_name.clone(); tokio::spawn(async move { - if let Err(e) = execute::inner_handler( + match execute::inner_handler( ExecuteRequest::Deploy(Deploy { - deployment: deployment.clone(), + deployment: deployment.name.clone(), stop_time: None, stop_signal: None, }), @@ -115,9 +116,37 @@ pub async fn update_deployment_cache( ) .await { - warn!( - "Failed to auto update Deployment {deployment} | {e:#}" - ) + Ok(_) => { + let ts = komodo_timestamp(); + let alert = Alert { + id: Default::default(), + ts, + resolved: true, + resolved_ts: ts.into(), + level: SeverityLevel::Ok, + target: ResourceTarget::Deployment(id.clone()), + data: AlertData::DeploymentAutoUpdated { + id, + name: deployment.name, + server_name, + server_id: deployment.config.server_id, + image, + }, + }; + let res = db_client().alerts.insert_one(&alert).await; + if let Err(e) = res { + error!( + "Failed to record DeploymentAutoUpdated to db | {e:#}" + ); + } + send_alerts(&[alert]).await; + } + Err(e) => { + warn!( + "Failed to auto update Deployment {} | {e:#}", + deployment.name + ) + } } }); } @@ -152,7 +181,7 @@ pub async fn update_deployment_cache( let res = db_client().alerts.insert_one(&alert).await; if let Err(e) = res { error!( - "Failed to record Deployment update avaialable to db | {e:#}" + "Failed to record DeploymentImageUpdateAvailable to db | {e:#}" ); } send_alerts(&[alert]).await; @@ -257,7 +286,7 @@ pub async fn update_stack_cache( let res = db_client().alerts.insert_one(&alert).await; if let Err(e) = res { error!( - "Failed to record Stack update avaialable to db | {e:#}" + "Failed to record StackImageUpdateAvailable to db | {e:#}" ); } send_alerts(&[alert]).await; @@ -271,21 +300,31 @@ pub async fn update_stack_cache( } StackService { service: service_name.clone(), - image: image.to_string(), + image: image.clone(), container, update_available, } }).collect::>(); - let update_available = - services_with_containers.iter().any(|service| { - service.update_available - // Only consider running services with available updates - && service - .container - .as_ref() - .map(|c| c.state == ContainerStateStatusEnum::Running) - .unwrap_or_default() - }); + + let mut update_available = false; + let mut images_with_update = Vec::new(); + + for service in services_with_containers.iter() { + if service.update_available { + images_with_update.push(service.image.clone()); + // Only allow it to actually trigger an auto update deploy + // if the service is running. + if service + .container + .as_ref() + .map(|c| c.state == ContainerStateStatusEnum::Running) + .unwrap_or_default() + { + update_available = true + } + } + } + let state = get_stack_state_from_containers( &stack.config.ignore_services, &services, @@ -301,11 +340,12 @@ pub async fn update_stack_cache( .busy() .unwrap_or(true) { - let stack = stack.name.clone(); + let id = stack.id.clone(); + let server_name = server_name.clone(); tokio::spawn(async move { - if let Err(e) = execute::inner_handler( + match execute::inner_handler( ExecuteRequest::DeployStack(DeployStack { - stack: stack.clone(), + stack: stack.name.clone(), service: None, stop_time: None, }), @@ -313,7 +353,34 @@ pub async fn update_stack_cache( ) .await { - warn!("Failed auto update Stack {stack} | {e:#}") + Ok(_) => { + let ts = komodo_timestamp(); + let alert = Alert { + id: Default::default(), + ts, + resolved: true, + resolved_ts: ts.into(), + level: SeverityLevel::Ok, + target: ResourceTarget::Stack(id.clone()), + data: AlertData::StackAutoUpdated { + id, + name: stack.name.clone(), + server_name, + server_id: stack.config.server_id, + images: images_with_update, + }, + }; + let res = db_client().alerts.insert_one(&alert).await; + if let Err(e) = res { + error!( + "Failed to record StackAutoUpdated to db | {e:#}" + ); + } + send_alerts(&[alert]).await; + } + Err(e) => { + warn!("Failed auto update Stack {} | {e:#}", stack.name) + } } }); } diff --git a/bin/periphery/src/compose.rs b/bin/periphery/src/compose.rs index d37954b25..4d75b9e5c 100644 --- a/bin/periphery/src/compose.rs +++ b/bin/periphery/src/compose.rs @@ -5,8 +5,14 @@ use command::run_komodo_command; use formatting::format_serror; use git::environment; use komodo_client::entities::{ - all_logs_success, environment_vars_from_str, stack::Stack, - to_komodo_name, update::Log, CloneArgs, FileContents, + all_logs_success, environment_vars_from_str, + stack::{ + ComposeFile, ComposeService, ComposeServiceDeploy, Stack, + StackServiceNames, + }, + to_komodo_name, + update::Log, + CloneArgs, FileContents, }; use periphery_client::api::{ compose::ComposeUpResponse, @@ -150,7 +156,66 @@ pub async fn compose_up( output }); - // Build images before destroying to minimize downtime. + // Uses 'docker compose config' command to extract services (including image) + // after performing interpolation + { + let command = format!( + "{docker_compose} -p {project_name} -f {file_args}{env_file}{additional_env_files} config --format json", + ); + let config_log = run_komodo_command( + "compose build", + run_directory.as_ref(), + command, + false, + ) + .await; + if !config_log.success { + res.logs.push(config_log); + return Err(anyhow!( + "Failed to validate compose files, stopping the run." + )); + } + let compose = + serde_json::from_str::(&config_log.stdout) + .context("Failed to parse compose contents")?; + for ( + service_name, + ComposeService { + container_name, + deploy, + image, + }, + ) in compose.services + { + let image = image.unwrap_or_default(); + match deploy { + Some(ComposeServiceDeploy { + replicas: Some(replicas), + }) if replicas > 1 => { + for i in 1..1 + replicas { + res.services.push(StackServiceNames { + container_name: format!( + "{project_name}-{service_name}-{i}" + ), + service_name: format!("{service_name}-{i}"), + image: image.clone(), + }); + } + } + _ => { + res.services.push(StackServiceNames { + container_name: container_name.unwrap_or_else(|| { + format!("{project_name}-{service_name}") + }), + service_name, + image, + }); + } + } + } + } + + // Build images before deploying. // If this fails, do not continue. if stack.config.run_build { let build_extra_args = @@ -198,7 +263,7 @@ pub async fn compose_up( } } - // + // Pull images before deploying if stack.config.auto_pull { // Pull images before destroying to minimize downtime. // If this fails, do not continue. diff --git a/client/core/rs/src/entities/alert.rs b/client/core/rs/src/entities/alert.rs index 78ecc4934..6311d0ebf 100644 --- a/client/core/rs/src/entities/alert.rs +++ b/client/core/rs/src/entities/alert.rs @@ -158,6 +158,20 @@ pub enum AlertData { image: String, }, + /// A Deployment has an image update available + DeploymentAutoUpdated { + /// The id of the deployment + id: String, + /// The name of the deployment + name: String, + /// The server id of server that the deployment is on + server_id: String, + /// The server name + server_name: String, + /// The updated image + image: String, + }, + /// A stack's state has changed unexpectedly. StackStateChange { /// The id of the stack @@ -190,6 +204,20 @@ pub enum AlertData { image: String, }, + /// A Stack was auto updated + StackAutoUpdated { + /// The id of the stack + id: String, + /// The name of the stack + name: String, + /// The server id of server that the stack is on + server_id: String, + /// The server name + server_name: String, + /// One or more images that were updated + images: Vec, + }, + /// An AWS builder failed to terminate. AwsBuilderTerminationFailed { /// The id of the aws instance which failed to terminate diff --git a/client/core/ts/package.json b/client/core/ts/package.json index f2732a5a7..2925f8f44 100644 --- a/client/core/ts/package.json +++ b/client/core/ts/package.json @@ -1,6 +1,6 @@ { "name": "komodo_client", - "version": "1.16.9", + "version": "1.16.10", "description": "Komodo client package", "homepage": "https://komo.do", "main": "dist/lib.js", diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index 32971ef35..413a569e3 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -786,8 +786,10 @@ export interface DeploymentConfig { /** Whether to poll for any updates to the image. */ poll_for_updates?: boolean; /** - * Whether to automatically redeploy when a - * newer image is found. + * Whether to automatically redeploy when + * newer a image is found. Will implicitly + * enable `poll_for_updates`, you don't need to + * enable both. */ auto_update?: boolean; /** Whether to send ContainerStateChange alerts for this deployment. */ @@ -1000,6 +1002,19 @@ export type AlertData = server_name: string; /** The image with update */ image: string; +}} + /** A Deployment has an image update available */ + | { type: "DeploymentAutoUpdated", data: { + /** The id of the deployment */ + id: string; + /** The name of the deployment */ + name: string; + /** The server id of server that the deployment is on */ + server_id: string; + /** The server name */ + server_name: string; + /** The updated image */ + image: string; }} /** A stack's state has changed unexpectedly. */ | { type: "StackStateChange", data: { @@ -1030,6 +1045,19 @@ export type AlertData = service: string; /** The image with update */ image: string; +}} + /** A Stack was auto updated */ + | { type: "StackAutoUpdated", data: { + /** The id of the stack */ + id: string; + /** The name of the stack */ + name: string; + /** The server id of server that the stack is on */ + server_id: string; + /** The server name */ + server_name: string; + /** One or more images that were updated */ + images: string[]; }} /** An AWS builder failed to terminate. */ | { type: "AwsBuilderTerminationFailed", data: { @@ -1549,8 +1577,10 @@ export interface StackConfig { /** Whether to poll for any updates to the images. */ poll_for_updates?: boolean; /** - * Whether to automatically redeploy when a - * newer images are found. + * Whether to automatically redeploy when + * newer images are found. Will implicitly + * enable `poll_for_updates`, you don't need to + * enable both. */ auto_update?: boolean; /** Whether to run `docker compose down` before `compose up`. */ diff --git a/client/periphery/rs/src/api/compose.rs b/client/periphery/rs/src/api/compose.rs index e283aab44..f5c49eac2 100644 --- a/client/periphery/rs/src/api/compose.rs +++ b/client/periphery/rs/src/api/compose.rs @@ -1,5 +1,5 @@ use komodo_client::entities::{ - stack::{ComposeProject, Stack}, + stack::{ComposeProject, Stack, StackServiceNames}, update::Log, FileContents, SearchCombinator, }; @@ -167,8 +167,13 @@ pub struct ComposeUpResponse { pub missing_files: Vec, /// The logs produced by the deploy pub logs: Vec, - /// whether stack was successfully deployed + /// Whether stack was successfully deployed pub deployed: bool, + /// The stack services. + /// + /// Note. The "image" is after interpolation. + #[serde(default)] + pub services: Vec, /// The deploy compose file contents if they could be acquired, or empty vec. pub file_contents: Vec, /// The error in getting remote file contents at the path, or null diff --git a/frontend/public/client/types.d.ts b/frontend/public/client/types.d.ts index 60eb57059..10fc207af 100644 --- a/frontend/public/client/types.d.ts +++ b/frontend/public/client/types.d.ts @@ -888,8 +888,10 @@ export interface DeploymentConfig { /** Whether to poll for any updates to the image. */ poll_for_updates?: boolean; /** - * Whether to automatically redeploy when a - * newer image is found. + * Whether to automatically redeploy when + * newer a image is found. Will implicitly + * enable `poll_for_updates`, you don't need to + * enable both. */ auto_update?: boolean; /** Whether to send ContainerStateChange alerts for this deployment. */ @@ -1107,6 +1109,22 @@ export type AlertData = image: string; }; } +/** A Deployment has an image update available */ + | { + type: "DeploymentAutoUpdated"; + data: { + /** The id of the deployment */ + id: string; + /** The name of the deployment */ + name: string; + /** The server id of server that the deployment is on */ + server_id: string; + /** The server name */ + server_name: string; + /** The updated image */ + image: string; + }; +} /** A stack's state has changed unexpectedly. */ | { type: "StackStateChange"; @@ -1143,6 +1161,22 @@ export type AlertData = image: string; }; } +/** A Stack was auto updated */ + | { + type: "StackAutoUpdated"; + data: { + /** The id of the stack */ + id: string; + /** The name of the stack */ + name: string; + /** The server id of server that the stack is on */ + server_id: string; + /** The server name */ + server_name: string; + /** One or more images that were updated */ + images: string[]; + }; +} /** An AWS builder failed to terminate. */ | { type: "AwsBuilderTerminationFailed"; @@ -1638,8 +1672,10 @@ export interface StackConfig { /** Whether to poll for any updates to the images. */ poll_for_updates?: boolean; /** - * Whether to automatically redeploy when a - * newer images are found. + * Whether to automatically redeploy when + * newer images are found. Will implicitly + * enable `poll_for_updates`, you don't need to + * enable both. */ auto_update?: boolean; /** Whether to run `docker compose down` before `compose up`. */ diff --git a/frontend/src/components/config/util.tsx b/frontend/src/components/config/util.tsx index 9b05a9810..24b7c1a0d 100644 --- a/frontend/src/components/config/util.tsx +++ b/frontend/src/components/config/util.tsx @@ -1164,30 +1164,20 @@ export const RenameResource = ({ const [name, set] = useState(""); return ( -
-
- Rename{" "} - {type === "ServerTemplate" - ? "Template" - : type === "ResourceSync" - ? "Sync" - : type} -
-
- set(e.target.value)} - className="w-96" - placeholder="Enter new name" - /> - } - disabled={!name || isPending} - loading={isPending} - onClick={() => mutate({ id, name })} - /> -
+
+ set(e.target.value)} + className="w-96" + placeholder="Enter new name" + /> + } + disabled={!name || isPending} + loading={isPending} + onClick={() => mutate({ id, name })} + />
); }; diff --git a/frontend/src/components/monaco.tsx b/frontend/src/components/monaco.tsx index f50d2f1a1..4f1581ec7 100644 --- a/frontend/src/components/monaco.tsx +++ b/frontend/src/components/monaco.tsx @@ -7,6 +7,7 @@ import * as prettier from "prettier/standalone"; import * as pluginTypescript from "prettier/plugins/typescript"; import * as pluginEstree from "prettier/plugins/estree"; import * as pluginYaml from "prettier/plugins/yaml"; +import { useWindowDimensions } from "@lib/hooks"; const MIN_EDITOR_HEIGHT = 56; // const MAX_EDITOR_HEIGHT = 500; @@ -39,6 +40,7 @@ export const MonacoEditor = ({ minHeight?: number; className?: string; }) => { + const dimensions = useWindowDimensions(); const [editor, setEditor] = useState(null); @@ -100,9 +102,13 @@ export const MonacoEditor = ({ const contentHeight = line_count * 18 + 30; const containerNode = editor.getContainerDomNode(); - containerNode.style.height = `${Math.max( - Math.ceil(contentHeight), - minHeight ?? MIN_EDITOR_HEIGHT + // containerNode.style.height = `${Math.max( + // Math.ceil(contentHeight), + // minHeight ?? MIN_EDITOR_HEIGHT + // )}px`; + containerNode.style.height = `${Math.min( + Math.max(Math.ceil(contentHeight), minHeight ?? MIN_EDITOR_HEIGHT), + Math.floor(dimensions.height * (3 / 5)) )}px`; }, [editor, line_count]); @@ -116,7 +122,7 @@ export const MonacoEditor = ({ const options: monaco.editor.IStandaloneEditorConstructionOptions = { minimap: { enabled: false }, - scrollbar: { alwaysConsumeMouseWheel: false }, + // scrollbar: { alwaysConsumeMouseWheel: false }, scrollBeyondLastLine: false, folding: false, automaticLayout: true, diff --git a/frontend/src/components/resources/alerter/config/alert_types.tsx b/frontend/src/components/resources/alerter/config/alert_types.tsx index 6de92104d..c1192303c 100644 --- a/frontend/src/components/resources/alerter/config/alert_types.tsx +++ b/frontend/src/components/resources/alerter/config/alert_types.tsx @@ -10,12 +10,14 @@ const ALERT_TYPES: Types.AlertData["type"][] = [ "ServerCpu", "ServerMem", "ServerDisk", - // State change - "ContainerStateChange", + // Stack "StackStateChange", - // Updates - "DeploymentImageUpdateAvailable", "StackImageUpdateAvailable", + "StackAutoUpdated", + // Deployment + "ContainerStateChange", + "DeploymentImageUpdateAvailable", + "DeploymentAutoUpdated", // Misc "AwsBuilderTerminationFailed", "ResourceSyncPendingUpdates", diff --git a/frontend/src/components/resources/deployment/config/index.tsx b/frontend/src/components/resources/deployment/config/index.tsx index b1b0fe8f4..8294b6884 100644 --- a/frontend/src/components/resources/deployment/config/index.tsx +++ b/frontend/src/components/resources/deployment/config/index.tsx @@ -6,6 +6,7 @@ import { AddExtraArgMenu, ConfigItem, ConfigList, + ConfigSwitch, InputList, } from "@components/config/util"; import { ImageConfig } from "./components/image"; @@ -46,6 +47,7 @@ export const DeploymentConfig = ({ const network = update.network ?? config.network; const hide_ports = network === "host" || network === "none"; + const auto_update = update.auto_update ?? config.auto_update ?? false; const disabled = global_disabled || perms !== Types.PermissionLevel.Write; @@ -229,9 +231,18 @@ export const DeploymentConfig = ({ }, { label: "Auto Update", + hidden: (update.image ?? config.image)?.type === "Build", components: { - poll_for_updates: !(update.auto_update ?? config.auto_update) && { - description: "Check for updates to the image on an interval.", + poll_for_updates: (poll, set) => { + return ( + set({ poll_for_updates })} + disabled={disabled || auto_update} + /> + ); }, auto_update: { description: "Trigger a redeploy if a newer image is found.", diff --git a/frontend/src/components/resources/resource-sync/config.tsx b/frontend/src/components/resources/resource-sync/config.tsx index 36370a8d0..8feaed20f 100644 --- a/frontend/src/components/resources/resource-sync/config.tsx +++ b/frontend/src/components/resources/resource-sync/config.tsx @@ -3,6 +3,7 @@ import { AccountSelectorConfig, ConfigItem, ConfigList, + ConfigSwitch, ProviderSelectorConfig, WebhookBuilder, } from "@components/config/util"; @@ -92,7 +93,7 @@ export const ResourceSyncConfig = ({ const integration = getWebhookIntegration(integrations, git_provider); const mode = getSyncMode(update, config); - const managed = update.managed ?? config.managed; + const managed = update.managed ?? config.managed ?? false; const setMode = (mode: SyncMode) => { if (mode === "Files On Server") { @@ -170,10 +171,16 @@ export const ResourceSyncConfig = ({ const general_common: ConfigComponent = { label: "General", components: { - delete: !managed && { - label: "Delete Unmatched Resources", - description: - "Executions will delete any resources not found in the resource files. Only use this when using one sync for everything.", + delete: (delete_mode, set) => { + return ( + set({ delete: delete_mode })} + disabled={disabled || managed} + /> + ); }, managed: { label: "Managed", diff --git a/frontend/src/components/resources/stack/config.tsx b/frontend/src/components/resources/stack/config.tsx index e02561167..c3e55b960 100644 --- a/frontend/src/components/resources/stack/config.tsx +++ b/frontend/src/components/resources/stack/config.tsx @@ -4,6 +4,7 @@ import { AddExtraArgMenu, ConfigItem, ConfigList, + ConfigSwitch, InputList, ProviderSelectorConfig, SystemCommand, @@ -193,6 +194,8 @@ export const StackConfig = ({ }, }; + const auto_update = update.auto_update ?? config.auto_update ?? false; + const general_common: ConfigComponent[] = [ { label: "Environment", @@ -239,8 +242,16 @@ export const StackConfig = ({ { label: "Auto Update", components: { - poll_for_updates: !(update.auto_update ?? config.auto_update) && { - description: "Check for updates to the image on an interval.", + poll_for_updates: (poll, set) => { + return ( + set({ poll_for_updates })} + disabled={disabled || auto_update} + /> + ); }, auto_update: { description: "Trigger a redeploy if a newer image is found.", diff --git a/frontend/src/lib/hooks.ts b/frontend/src/lib/hooks.ts index 5093e9518..9eba98d38 100644 --- a/frontend/src/lib/hooks.ts +++ b/frontend/src/lib/hooks.ts @@ -463,3 +463,22 @@ const WEBHOOK_ID_OR_NAME_ATOM = atomWithStorage( export const useWebhookIdOrName = () => { return useAtom(WEBHOOK_ID_OR_NAME_ATOM); }; + +export type Dimensions = { width: number; height: number }; +export const useWindowDimensions = () => { + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); + useEffect(() => { + const callback = () => { + setDimensions({ + width: window.screen.availWidth, + height: window.screen.availHeight, + }); + } + callback(); + window.addEventListener("resize", callback); + return () => { + window.removeEventListener("resize", callback); + } + }, []); + return dimensions; +} \ No newline at end of file diff --git a/frontend/src/pages/alerts.tsx b/frontend/src/pages/alerts.tsx index f81b420a8..6087ef7af 100644 --- a/frontend/src/pages/alerts.tsx +++ b/frontend/src/pages/alerts.tsx @@ -27,8 +27,12 @@ import { ResourceSelector } from "@components/resources/common"; const ALERT_TYPES_BY_RESOURCE: { [key: string]: Types.AlertData["type"][] } = { Server: ["ServerUnreachable", "ServerCpu", "ServerMem", "ServerDisk"], - Stack: ["StackStateChange", "StackImageUpdateAvailable"], - Deployment: ["ContainerStateChange", "DeploymentImageUpdateAvailable"], + Stack: ["StackStateChange", "StackImageUpdateAvailable", "StackAutoUpdated"], + Deployment: [ + "ContainerStateChange", + "DeploymentImageUpdateAvailable", + "DeploymentAutoUpdated", + ], Build: ["BuildFailed"], Repo: ["RepoBuildFailed"], ResourceSync: ["ResourceSyncPendingUpdates"],