mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-05 00:00:45 +00:00
v1.13.1 improve stack edge cases, and UI action responsiveness (#26)
* get stack state from project * move custom image name / tag below image setting for build config * services also trigger stack action state * add status to stack page * 1.13.1 patch
This commit is contained in:
Generated
+13
-13
@@ -41,7 +41,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
@@ -967,7 +967,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"monitor_client",
|
||||
"run_command",
|
||||
@@ -1351,7 +1351,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -1482,7 +1482,7 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
@@ -2085,7 +2085,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -2154,7 +2154,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2289,7 +2289,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2309,7 +2309,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2341,7 +2341,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_core"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2399,7 +2399,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2835,7 +2835,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -3987,7 +3987,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenvy",
|
||||
@@ -4594,7 +4594,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"logger",
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -15,7 +15,7 @@ monitor_client = { path = "client/core/rs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
# LOCAL
|
||||
monitor_client = "1.13.0"
|
||||
monitor_client = "1.13.1"
|
||||
periphery_client = { path = "client/periphery/rs" }
|
||||
formatting = { path = "lib/formatting" }
|
||||
command = { path = "lib/command" }
|
||||
|
||||
@@ -200,16 +200,11 @@ impl Resolve<StartStack, (User, Update)> for State {
|
||||
StartStack { stack, service }: StartStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<StartStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.starting = true
|
||||
}
|
||||
},
|
||||
|state| state.starting = true,
|
||||
update,
|
||||
(),
|
||||
)
|
||||
@@ -224,15 +219,12 @@ impl Resolve<RestartStack, (User, Update)> for State {
|
||||
RestartStack { stack, service }: RestartStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<RestartStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.restarting = true;
|
||||
}
|
||||
state.restarting = true;
|
||||
},
|
||||
update,
|
||||
(),
|
||||
@@ -248,16 +240,11 @@ impl Resolve<PauseStack, (User, Update)> for State {
|
||||
PauseStack { stack, service }: PauseStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<PauseStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.pausing = true
|
||||
}
|
||||
},
|
||||
|state| state.pausing = true,
|
||||
update,
|
||||
(),
|
||||
)
|
||||
@@ -272,16 +259,11 @@ impl Resolve<UnpauseStack, (User, Update)> for State {
|
||||
UnpauseStack { stack, service }: UnpauseStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<UnpauseStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.unpausing = true
|
||||
}
|
||||
},
|
||||
|state| state.unpausing = true,
|
||||
update,
|
||||
(),
|
||||
)
|
||||
@@ -300,16 +282,11 @@ impl Resolve<StopStack, (User, Update)> for State {
|
||||
}: StopStack,
|
||||
(user, update): (User, Update),
|
||||
) -> anyhow::Result<Update> {
|
||||
let no_service = service.is_none();
|
||||
execute_compose::<StopStack>(
|
||||
&stack,
|
||||
service,
|
||||
&user,
|
||||
|state| {
|
||||
if no_service {
|
||||
state.stopping = true
|
||||
}
|
||||
},
|
||||
|state| state.stopping = true,
|
||||
update,
|
||||
stop_time,
|
||||
)
|
||||
|
||||
@@ -214,6 +214,8 @@ impl Resolve<GetStacksSummary, User> for State {
|
||||
StackState::Paused => res.paused += 1,
|
||||
StackState::Stopped => res.stopped += 1,
|
||||
StackState::Restarting => res.restarting += 1,
|
||||
StackState::Created => res.created += 1,
|
||||
StackState::Removing => res.removing += 1,
|
||||
StackState::Dead => res.dead += 1,
|
||||
StackState::Unhealthy => res.unhealthy += 1,
|
||||
StackState::Down => res.down += 1,
|
||||
|
||||
@@ -5,13 +5,13 @@ use monitor_client::entities::{
|
||||
alerter::Alerter,
|
||||
build::Build,
|
||||
builder::Builder,
|
||||
deployment::{ContainerSummary, Deployment, DeploymentState},
|
||||
deployment::{Deployment, DeploymentState},
|
||||
permission::PermissionLevel,
|
||||
procedure::Procedure,
|
||||
repo::Repo,
|
||||
server::{Server, ServerState},
|
||||
server_template::ServerTemplate,
|
||||
stack::{Stack, StackServiceNames, StackState},
|
||||
stack::{ComposeProject, Stack, StackState},
|
||||
sync::ResourceSync,
|
||||
tag::Tag,
|
||||
update::{ResourceTarget, ResourceTargetVariant, Update},
|
||||
@@ -33,11 +33,6 @@ use crate::{
|
||||
state::db_client,
|
||||
};
|
||||
|
||||
use super::stack::{
|
||||
compose_container_match_regex,
|
||||
services::extract_services_from_stack,
|
||||
};
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
// user: Id or username
|
||||
pub async fn get_user(user: &str) -> anyhow::Result<User> {
|
||||
@@ -98,63 +93,51 @@ pub async fn get_deployment_state(
|
||||
}
|
||||
|
||||
/// Can pass all the containers from the same server
|
||||
pub fn get_stack_state_from_containers(
|
||||
services: &[StackServiceNames],
|
||||
containers: &[ContainerSummary],
|
||||
pub fn get_stack_state_from_projects(
|
||||
stack: &Stack,
|
||||
projects: &[ComposeProject],
|
||||
) -> StackState {
|
||||
// first filter the containers to only ones which match the service
|
||||
let containers = containers.iter().filter(|container| {
|
||||
services.iter().any(|StackServiceNames { service_name, container_name }| {
|
||||
match compose_container_match_regex(container_name)
|
||||
.with_context(|| format!("failed to construct container name matching regex for service {service_name}"))
|
||||
{
|
||||
Ok(regex) => regex,
|
||||
Err(e) => {
|
||||
warn!("{e:#}");
|
||||
return false
|
||||
}
|
||||
}.is_match(&container.name)
|
||||
let project_name = stack.project_name(false);
|
||||
let Some(status) = projects
|
||||
.iter()
|
||||
.find(|project| project.name == project_name)
|
||||
.and_then(|project| project.status.as_deref())
|
||||
else {
|
||||
return StackState::Down;
|
||||
};
|
||||
let Ok(states) = status
|
||||
.split(", ")
|
||||
.filter_map(|state| state.split('(').next())
|
||||
.map(|state| {
|
||||
state.parse::<DeploymentState>().with_context(|| {
|
||||
format!("failed to parse stack state entry: {state}")
|
||||
})
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
if containers.is_empty() {
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.inspect_err(|e| warn!("{e:#}"))
|
||||
else {
|
||||
return StackState::Unknown;
|
||||
};
|
||||
if states.is_empty() {
|
||||
return StackState::Down;
|
||||
}
|
||||
if services.len() != containers.len() {
|
||||
if states.len() > 1 {
|
||||
return StackState::Unhealthy;
|
||||
}
|
||||
let running = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Running);
|
||||
if running {
|
||||
return StackState::Running;
|
||||
match states[0] {
|
||||
DeploymentState::Unknown => StackState::Unknown,
|
||||
DeploymentState::NotDeployed => StackState::Down,
|
||||
DeploymentState::Created => StackState::Created,
|
||||
DeploymentState::Restarting => StackState::Restarting,
|
||||
DeploymentState::Running => StackState::Running,
|
||||
DeploymentState::Removing => StackState::Removing,
|
||||
DeploymentState::Paused => StackState::Paused,
|
||||
DeploymentState::Exited => StackState::Stopped,
|
||||
DeploymentState::Dead => StackState::Dead,
|
||||
}
|
||||
let paused = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Paused);
|
||||
if paused {
|
||||
return StackState::Paused;
|
||||
}
|
||||
let stopped = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Exited);
|
||||
if stopped {
|
||||
return StackState::Stopped;
|
||||
}
|
||||
let restarting = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Restarting);
|
||||
if restarting {
|
||||
return StackState::Restarting;
|
||||
}
|
||||
let dead = containers
|
||||
.iter()
|
||||
.all(|container| container.state == DeploymentState::Dead);
|
||||
if dead {
|
||||
return StackState::Dead;
|
||||
}
|
||||
StackState::Unhealthy
|
||||
}
|
||||
|
||||
/// Gets stack state fresh from periphery
|
||||
#[instrument(level = "debug")]
|
||||
pub async fn get_stack_state(
|
||||
stack: &Stack,
|
||||
@@ -167,13 +150,11 @@ pub async fn get_stack_state(
|
||||
if status != ServerState::Ok {
|
||||
return Ok(StackState::Unknown);
|
||||
}
|
||||
let containers = super::periphery_client(&server)?
|
||||
.request(periphery_client::api::container::GetContainerList {})
|
||||
let projects = super::periphery_client(&server)?
|
||||
.request(periphery_client::api::compose::ListComposeProjects {})
|
||||
.await?;
|
||||
|
||||
let services = extract_services_from_stack(stack, false).await?;
|
||||
|
||||
Ok(get_stack_state_from_containers(&services, &containers))
|
||||
Ok(get_stack_state_from_projects(stack, &projects))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug")]
|
||||
|
||||
@@ -214,7 +214,7 @@ pub async fn update_cache_for_server(server: &Server) {
|
||||
Ok((containers, networks, images, projects)) => {
|
||||
tokio::join!(
|
||||
resources::update_deployment_cache(deployments, &containers),
|
||||
resources::update_stack_cache(stacks, &containers),
|
||||
resources::update_stack_cache(stacks, &containers, &projects),
|
||||
);
|
||||
insert_server_status(
|
||||
server,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use anyhow::Context;
|
||||
use monitor_client::entities::{
|
||||
deployment::{ContainerSummary, Deployment, DeploymentState},
|
||||
stack::{Stack, StackService, StackServiceNames},
|
||||
stack::{ComposeProject, Stack, StackService, StackServiceNames},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
helpers::{
|
||||
query::get_stack_state_from_containers,
|
||||
query::get_stack_state_from_projects,
|
||||
stack::{
|
||||
compose_container_match_regex,
|
||||
services::extract_services_from_stack,
|
||||
@@ -55,6 +55,7 @@ pub async fn update_deployment_cache(
|
||||
pub async fn update_stack_cache(
|
||||
stacks: Vec<Stack>,
|
||||
containers: &[ContainerSummary],
|
||||
projects: &[ComposeProject],
|
||||
) {
|
||||
let stack_status_cache = stack_status_cache();
|
||||
for stack in stacks {
|
||||
@@ -92,7 +93,7 @@ pub async fn update_stack_cache(
|
||||
.map(|s| s.curr.state);
|
||||
let status = CachedStackStatus {
|
||||
id: stack.id.clone(),
|
||||
state: get_stack_state_from_containers(&services, containers),
|
||||
state: get_stack_state_from_projects(&stack, projects),
|
||||
services: services_with_containers,
|
||||
};
|
||||
stack_status_cache
|
||||
|
||||
@@ -67,28 +67,32 @@ impl super::MonitorResource for Stack {
|
||||
.map(|service| service.service_name)
|
||||
.collect();
|
||||
// This is only true if it is KNOWN to be true. so other cases are false.
|
||||
let (project_missing, status) = if stack.config.server_id.is_empty()
|
||||
|| matches!(state, StackState::Down | StackState::Unknown)
|
||||
{
|
||||
(false, None)
|
||||
} else if let Some(status) = server_status_cache()
|
||||
.get(&stack.config.server_id)
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
if let Some(projects) = &status.projects {
|
||||
if let Some(project) = projects.iter().find(|project| project.name == project_name) {
|
||||
(false, project.status.clone())
|
||||
let (project_missing, status) =
|
||||
if stack.config.server_id.is_empty()
|
||||
|| matches!(state, StackState::Down | StackState::Unknown)
|
||||
{
|
||||
(false, None)
|
||||
} else if let Some(status) = server_status_cache()
|
||||
.get(&stack.config.server_id)
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
if let Some(projects) = &status.projects {
|
||||
if let Some(project) = projects
|
||||
.iter()
|
||||
.find(|project| project.name == project_name)
|
||||
{
|
||||
(false, project.status.clone())
|
||||
} else {
|
||||
// The project doesn't exist
|
||||
(true, None)
|
||||
}
|
||||
} else {
|
||||
// The project doesn't exist
|
||||
(true, None)
|
||||
(false, None)
|
||||
}
|
||||
} else {
|
||||
(false, None)
|
||||
}
|
||||
} else {
|
||||
(false, None)
|
||||
};
|
||||
};
|
||||
StackListItem {
|
||||
id: stack.id,
|
||||
name: stack.name,
|
||||
|
||||
@@ -213,6 +213,10 @@ pub struct GetStacksSummaryResponse {
|
||||
pub restarting: u32,
|
||||
/// The number of stacks with Dead state.
|
||||
pub dead: u32,
|
||||
/// The number of stacks with Created state.
|
||||
pub created: u32,
|
||||
/// The number of stacks with Removing state.
|
||||
pub removing: u32,
|
||||
/// The number of stacks with Unhealthy state.
|
||||
pub unhealthy: u32,
|
||||
/// The number of stacks with Down state.
|
||||
|
||||
@@ -107,10 +107,14 @@ pub enum StackState {
|
||||
Paused,
|
||||
/// All contianers are stopped
|
||||
Stopped,
|
||||
/// All containers are created
|
||||
Created,
|
||||
/// All containers are restarting
|
||||
Restarting,
|
||||
/// All containers are dead
|
||||
Dead,
|
||||
/// All containers are removing
|
||||
Removing,
|
||||
/// The containers are in a mix of states
|
||||
Unhealthy,
|
||||
/// The stack is not deployed
|
||||
|
||||
@@ -113,20 +113,6 @@ export const BuildConfig = ({
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Custom Name / Tag",
|
||||
components: {
|
||||
image_name: {
|
||||
description: "Optional. Push the image under a different name",
|
||||
placeholder: "Custom image name",
|
||||
},
|
||||
image_tag: {
|
||||
description:
|
||||
"Optional. Postfix the image version with a custom tag.",
|
||||
placeholder: "Custom image tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Git",
|
||||
components: {
|
||||
@@ -194,6 +180,20 @@ export const BuildConfig = ({
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Custom Name / Tag",
|
||||
components: {
|
||||
image_name: {
|
||||
description: "Optional. Push the image under a different name",
|
||||
placeholder: "Custom image name",
|
||||
},
|
||||
image_tag: {
|
||||
description:
|
||||
"Optional. Postfix the image version with a custom tag.",
|
||||
placeholder: "Custom image tag",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Extra Args",
|
||||
description: (
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { useInvalidate, useLocalStorage, useRead, useWrite } from "@lib/hooks";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card } from "@ui/card";
|
||||
import { FolderGit, Layers, Loader2, RefreshCcw, Server } from "lucide-react";
|
||||
import {
|
||||
FolderGit,
|
||||
Layers,
|
||||
Loader2,
|
||||
NotepadText,
|
||||
RefreshCcw,
|
||||
Server,
|
||||
} from "lucide-react";
|
||||
import { StackConfig } from "./config";
|
||||
import { DeleteResource, NewResource, ResourceLink } from "../common";
|
||||
import { StackTable } from "./table";
|
||||
@@ -132,6 +139,15 @@ export const StackComponents: RequiredResourceComponents = {
|
||||
}
|
||||
return <StatusBadge text={state} intent={stack_state_intention(state)} />;
|
||||
},
|
||||
Status: ({ id }) => {
|
||||
const info = useStack(id)?.info;
|
||||
if (info?.state !== Types.StackState.Unhealthy) return null;
|
||||
return (
|
||||
info?.status && (
|
||||
<p className="text-sm text-muted-foreground">{info.status}</p>
|
||||
)
|
||||
);
|
||||
},
|
||||
NoConfig: ({ id }) => {
|
||||
const config = useFullStack(id)?.config;
|
||||
if (config?.file_contents || config?.repo) {
|
||||
@@ -148,9 +164,9 @@ export const StackComponents: RequiredResourceComponents = {
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent align="start">
|
||||
<div className="grid gap-2">
|
||||
No configuration provided for stack. Cannot get stack state. Either
|
||||
paste the compose file contents into the UI, or configure a git repo
|
||||
containing your files.
|
||||
No configuration provided for stack. Cannot get stack state.
|
||||
Either paste the compose file contents into the UI, or configure a
|
||||
git repo containing your files.
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
@@ -286,21 +302,32 @@ export const StackComponents: RequiredResourceComponents = {
|
||||
},
|
||||
|
||||
Info: {
|
||||
Repo: ({ id }) => {
|
||||
const repo = useStack(id)?.info.repo;
|
||||
Contents: ({ id }) => {
|
||||
const config = useFullStack(id)?.config;
|
||||
const file_contents = config?.file_contents;
|
||||
if (file_contents) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<NotepadText className="w-4 h-4" />
|
||||
Local
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderGit className="w-4 h-4" />
|
||||
{repo}
|
||||
{config?.repo}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
// Branch: ({ id }) => {
|
||||
// const branch = useStack(id)?.info.branch;
|
||||
// const config = useFullStack(id)?.config;
|
||||
// const file_contents = config?.file_contents;
|
||||
// if (file_contents || !config?.branch) return null
|
||||
// return (
|
||||
// <div className="flex items-center gap-2">
|
||||
// <FolderGit className="w-4 h-4" />
|
||||
// {branch}
|
||||
// <GitBranch className="w-4 h-4" />
|
||||
// {config.branch}
|
||||
// </div>
|
||||
// );
|
||||
// },
|
||||
|
||||
Reference in New Issue
Block a user