* 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>
This commit is contained in:
Maxwell Becker
2025-06-06 23:08:51 -07:00
committed by GitHub
parent 4165e25332
commit 4d401d7f20
93 changed files with 2196 additions and 1264 deletions

26
Cargo.lock generated
View File

@@ -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",

View File

@@ -8,7 +8,7 @@ members = [
]
[workspace.package]
version = "1.18.0"
version = "1.18.1"
edition = "2024"
authors = ["mbecker20 <becker.maxh@gmail.com>"]
license = "GPL-3.0-or-later"

View File

@@ -12,7 +12,7 @@ use crate::{
};
pub enum ExecutionResult {
Single(Update),
Single(Box<Update>),
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);

View File

@@ -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(),

View File

@@ -174,8 +174,11 @@ async fn handler(
Ok((TypedHeader(ContentType::json()), res))
}
#[typeshare(serialized_as = "Update")]
type BoxUpdate = Box<Update>;
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()))
})
}

View File

@@ -163,7 +163,7 @@ impl Resolve<ExecuteArgs> for CloneRepo {
impl super::BatchExecute for BatchPullRepo {
type Resource = Repo;
fn single_request(repo: String) -> ExecuteRequest {
ExecuteRequest::CloneRepo(CloneRepo { repo })
ExecuteRequest::PullRepo(PullRepo { repo })
}
}

View File

@@ -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),

View File

@@ -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<ReadArgs> for ListSchedules {
async fn resolve(
self,
args: &ReadArgs,
) -> serror::Result<Vec<Schedule>> {
let all_tags = get_all_tags(None).await?;
let (actions, procedures) = tokio::try_join!(
list_full_for_user::<Action>(
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::<Procedure>(
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>(&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>(&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(),
)
}
}

View File

@@ -176,7 +176,7 @@ impl Resolve<ReadArgs> 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 {

View File

@@ -209,7 +209,7 @@ impl Resolve<WriteArgs> 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(

View File

@@ -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
}

View File

@@ -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<DeploymentState> {
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<R: KomodoResource>(
id: &String,
) -> anyhow::Result<Option<i64>> {
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()
}

View File

@@ -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<HashMap<(String, AlertDataVariant), bool>>,
}
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<AlertBuffer> = OnceLock::new();
ALERT_BUFFER.get_or_init(Default::default)
}

View File

@@ -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(

View File

@@ -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::Config, Self::Info>,
) -> 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>(&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()

View File

@@ -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,
),

View File

@@ -78,6 +78,20 @@ impl super::KomodoResource for Deployment {
deployment: Resource<Self::Config, Self::Info>,
) -> 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<Self::Config, Self::Info>,
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(),
),
),
};

View File

@@ -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::Config, Self::Info>,
) -> 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>(&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()

View File

@@ -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,

View File

@@ -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::Config, Self::Info>,
) -> 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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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<super::Args> for GetLatestCommit {
async fn resolve(
self,
_: &super::Args,
) -> serror::Result<LatestCommit> {
) -> serror::Result<Option<LatestCommit>> {
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?))
}
}

View File

@@ -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

View File

@@ -160,14 +160,14 @@ pub enum BatchExecutionResponseItem {
Err(BatchExecutionResponseItemErr),
}
impl From<Result<Update, BatchExecutionResponseItemErr>>
impl From<Result<Box<Update>, BatchExecutionResponseItemErr>>
for BatchExecutionResponseItem
{
fn from(
value: Result<Update, BatchExecutionResponseItemErr>,
value: Result<Box<Update>, BatchExecutionResponseItemErr>,
) -> Self {
match value {
Ok(update) => Self::Ok(update),
Ok(update) => Self::Ok(*update),
Err(e) => Self::Err(e),
}
}

View File

@@ -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::*;

View File

@@ -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<String>,
/// 'All' or 'Any'
#[serde(default)]
pub tag_behavior: TagBehavior,
}
#[typeshare]
pub type ListSchedulesResponse = Vec<Schedule>;

View File

@@ -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<ActionListItemInfo>;
#[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<I64>,
/// If the action has schedule enabled, this is the
/// next scheduled run time in unix ms.
pub next_scheduled_run: Option<I64>,
/// If there is an error parsing schedule expression,
@@ -53,15 +53,7 @@ pub enum ActionState {
}
#[typeshare]
pub type Action = Resource<ActionConfig, ActionInfo>;
#[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<ActionConfig, NoData>;
#[typeshare(serialized_as = "Partial<ActionConfig>")]
pub type _PartialActionConfig = PartialActionConfig;

View File

@@ -45,11 +45,15 @@ pub struct BuildListItemInfo {
pub files_on_host: bool,
/// The git provider domain
pub git_provider: Option<String>,
pub git_provider: String,
/// The repo used as the source of the build
pub repo: Option<String>,
pub repo: String,
/// The branch of the repo
pub branch: Option<String>,
pub branch: String,
/// Full link to the repo.
pub repo_link: String,
/// Latest built short commit hash, or null.
pub built_hash: Option<String>,
/// Latest short commit hash, or null. Only for repo based stacks

View File

@@ -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<String>,

View File

@@ -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<ContainerStateStatusEnum> for DeploymentState {

View File

@@ -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]

View File

@@ -159,7 +159,7 @@ pub enum SpecificPermission {
impl SpecificPermission {
fn all() -> IndexSet<SpecificPermission> {
SpecificPermission::VARIANTS.into_iter().cloned().collect()
SpecificPermission::VARIANTS.iter().cloned().collect()
}
}

View File

@@ -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<I64>,
/// If the procedure has schedule enabled, this is the
/// next scheduled run time in unix ms.
pub next_scheduled_run: Option<I64>,

View File

@@ -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.

View File

@@ -106,6 +106,7 @@ pub struct ResourceQuery<T: Default> {
/// Pass Vec of tag ids or tag names
#[serde(default, deserialize_with = "string_list_deserializer")]
pub tags: Vec<String>,
/// 'All' or 'Any'
#[serde(default)]
pub tag_behavior: TagBehavior,
#[serde(default)]

View File

@@ -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<I64>,
/// Next scheduled run time in unix ms.
pub next_scheduled_run: Option<I64>,
/// If there is an error parsing schedule expression,
/// it will be given here.
pub schedule_error: Option<String>,
/// Resource tags.
pub tags: Vec<String>,
}

View File

@@ -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,
}

View File

@@ -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<String>,
/// Commit message of last sync, or empty string

View File

@@ -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",

View File

@@ -50,6 +50,9 @@ export type ReadResponses = {
ListActions: Types.ListActionsResponse;
ListFullActions: Types.ListFullActionsResponse;
// ==== SCHEDULE ====
ListSchedules: Types.ListSchedulesResponse;
// ==== SERVER ====
GetServersSummary: Types.GetServersSummaryResponse;
GetServer: Types.GetServerResponse;

View File

@@ -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<ActionConfig, ActionInfo>;
export type Action = Resource<ActionConfig, NoData>;
export interface ResourceListItem<Info> {
/** 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<T> {
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<BuilderConfig, undefined>;
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<ProcedureConfig, undefined>;
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<DeploymentConfig, undefined>;
* - 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<ResourceSyncListItemInfo>;
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<SpecificPermission>;
specific?: Array<SpecificPermission>;
}
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",
/**

View File

@@ -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<LatestCommit>)]
#[error(serror::Error)]
pub struct GetLatestCommit {
pub name: String,

View File

@@ -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;

View File

@@ -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<ActionConfig, ActionInfo>;
export type Action = Resource<ActionConfig, NoData>;
export interface ResourceListItem<Info> {
/** 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<T> {
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<BuilderConfig, undefined>;
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<ProcedureConfig, undefined>;
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<DeploymentConfig, undefined>;
* - 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<ResourceSyncListItemInfo>;
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<SpecificPermission>;
specific?: Array<SpecificPermission>;
}
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",
/**

View File

@@ -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";
/**

View File

@@ -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 <StatusBadge text={state} intent={build_state_intention(state)} />;
},
Info: {
Builder: ({ id }) => {
const info = useBuild(id)?.info;
const builder = useBuilder(info?.builder_id);
return builder?.id ? (
<ResourceLink type="Builder" id={builder?.id} />
) : (
<div className="flex gap-2 items-center text-sm">
<Factory className="w-4 h-4" />
<div>Unknown Builder</div>
</div>
);
},
Source: ({ id }) => {
const info = useBuild(id)?.info;
return <StandardSource info={info} />;
},
Branch: ({ id }) => {
const branch = useBuild(id)?.info.branch;
return (
<div className="flex items-center gap-2">
<FolderGit className="w-4 h-4" />
{branch}
</div>
);
},
},
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 ? (
<ResourceLink type="Builder" id={builder?.id} />
) : (
<div className="flex gap-2 items-center text-sm">
<Factory className="w-4 h-4" />
<div>Unknown Builder</div>
</div>
);
},
Repo: ({ id }) => {
const repo = useBuild(id)?.info.repo;
return (
<div className="flex items-center gap-2">
<FolderGit className="w-4 h-4" />
{repo}
</div>
);
},
Branch: ({ id }) => {
const branch = useBuild(id)?.info.branch;
return (
<div className="flex items-center gap-2">
<FolderGit className="w-4 h-4" />
{branch}
</div>
);
},
},
Actions: { RunBuild },
Page: {},

View File

@@ -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 }) => (
<SortableHeader column={column} title="Name" />
),
accessorKey: "name",
cell: ({ row }) => <ResourceLink type="Build" id={row.original.id} />,
size: 200,
},
{
// header: ({ column }) => (
// <SortableHeader column={column} title="Source" />
// ),
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 }) => (
<SortableHeader column={column} title="Source" />
),
accessorKey: "info.repo",
cell: ({ row }) => <StandardSource info={row.original.info} />,
size: 200,
},
{

View File

@@ -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 <ServerIcon server_id={info.instance_type} size={size} />;
} else {
return <Factory className={`w-${size} h-${size}`} />;
}
};
const ServerIcon = ({
server_id,
size,
}: {
server_id: string;
size: number;
}) => {
const state = useServer(server_id)?.info.state;
return (
<Factory
className={cn(
`w-${size} h-${size}`,
state && stroke_color_class_by_intention(server_state_intention(state))
)}
/>
);
};
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 (
<div className="flex items-center gap-2">
<Bot className="w-4 h-4" />
{info?.instance_type}
</div>
);
}
};
@@ -120,25 +159,23 @@ export const BuilderComponents: RequiredResourceComponents = {
<BuilderTable builders={resources as Types.BuilderListItem[]} />
),
Icon: () => <Factory className="w-4 h-4" />,
BigIcon: () => <Factory className="w-8 h-8" />,
Icon: ({ id }) => <Icon id={id} size={4} />,
BigIcon: ({ id }) => <Icon id={id} size={8} />,
State: () => null,
Status: {},
Info: {
Provider: ({ id }) => (
<div className="flex items-center gap-2">
<Cloud className="w-4 h-4" />
{useBuilder(id)?.info.builder_type}
</div>
),
InstanceType: ({ id }) => (
<div className="flex items-center gap-2">
<Bot className="w-4 h-4" />
<BuilderInstanceType id={id} />
</div>
),
Provider: ({ id }) => {
const builder_type = useBuilder(id)?.info.builder_type;
return (
<div className="flex items-center gap-2">
<Cloud className="w-4 h-4" />
{builder_type}
</div>
);
},
InstanceType: ({ id }) => <BuilderInstanceType id={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 (
<ServerInnerResourcePageHeader
builder={builder}
server_id={builder.info.instance_type}
/>
);
}
return (
<ResourcePageHeader
intent="None"
icon={<Factory className="w-8" />}
type="Builder"
<InnerResourcePageHeader
id={id}
name={builder?.name}
state={builder?.info.builder_type}
status={builder?.info.instance_type}
builder={builder}
intent="None"
icon={<Factory className="w-8 h-8" />}
/>
);
},
};
const ServerInnerResourcePageHeader = ({
builder,
server_id,
}: {
builder: Types.BuilderListItem;
server_id: string;
}) => {
const state = useServer(server_id)?.info.state;
return (
<InnerResourcePageHeader
id={builder.id}
builder={builder}
intent={server_state_intention(state)}
icon={<ServerIcon server_id={server_id} size={8} />}
/>
);
};
const InnerResourcePageHeader = ({
id,
builder,
intent,
icon,
}: {
id: string;
builder: Types.BuilderListItem | undefined;
intent: ColorIntention;
icon: ReactNode;
}) => {
return (
<ResourcePageHeader
intent={intent}
icon={icon}
type="Builder"
id={id}
name={builder?.name}
state={builder?.info.builder_type}
status={
builder?.info.builder_type === "Aws"
? builder?.info.instance_type
: undefined
}
/>
);
};

View File

@@ -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
/>
</div>
);
@@ -374,3 +385,36 @@ export const CopyWebhook = ({
</div>
);
};
export const StandardSource = ({
info,
}: {
info:
| {
files_on_host: boolean;
repo: string;
repo_link: string;
}
| undefined;
}) => {
if (!info) {
return <Loader2 className="w-4 h-4 animate-spin" />;
}
if (info.files_on_host) {
return (
<div className="flex items-center gap-2">
<Server className="w-4 h-4" />
Files on Server
</div>
);
}
if (info.repo) {
return <RepoLink repo={info.repo} link={info.repo_link} />;
}
return (
<div className="flex items-center gap-2">
<NotepadText className="w-4 h-4" />
UI Defined
</div>
);
};

View File

@@ -169,6 +169,7 @@ export const ImageConfig = ({
},
})
}
disabled={disabled}
/>
<BuildVersionSelector
buildId={image.params.build_id}

View File

@@ -245,11 +245,19 @@ export const DeploymentComponents: RequiredResourceComponents = {
);
},
Status: {
UpdateAvailable: ({ id }) => <UpdateAvailable id={id} />,
},
Info: {
Server: ({ id }) => {
const info = useDeployment(id)?.info;
const server = useServer(info?.server_id);
return server?.id ? (
<ResourceLink type="Server" id={server?.id} />
) : (
<div className="flex gap-2 items-center text-sm">
<Server className="w-4 h-4" />
<div>Unknown Server</div>
</div>
);
},
Image: ({ id }) => {
const config = useFullDeployment(id)?.config;
const info = useDeployment(id)?.info;
@@ -271,18 +279,6 @@ export const DeploymentComponents: RequiredResourceComponents = {
</div>
);
},
Server: ({ id }) => {
const info = useDeployment(id)?.info;
const server = useServer(info?.server_id);
return server?.id ? (
<ResourceLink type="Server" id={server?.id} />
) : (
<div className="flex gap-2 items-center text-sm">
<Server className="w-4 h-4" />
<div>Unknown Server</div>
</div>
);
},
Container: ({ id }) => {
const deployment = useDeployment(id);
if (
@@ -303,6 +299,10 @@ export const DeploymentComponents: RequiredResourceComponents = {
},
},
Status: {
UpdateAvailable: ({ id }) => <UpdateAvailable id={id} />,
},
Actions: {
RunBuild: ({ id }) => {
const build_id = useDeployment(id)?.info.build_id;

View File

@@ -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;

View File

@@ -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 <StatusBadge text={state} intent={repo_state_intention(state)} />;
},
Info: {
Target: ({ id }) => {
const info = useRepo(id)?.info;
const server = useServer(info?.server_id);
const builder = useBuilder(info?.builder_id);
return (
<div className="flex items-center gap-x-4 gap-y-2 flex-wrap">
{server?.id &&
(builder?.id ? (
<div className="pr-4 text-sm border-r">
<ResourceLink type="Server" id={server.id} />
</div>
) : (
<ResourceLink type="Server" id={server.id} />
))}
{builder?.id && <ResourceLink type="Builder" id={builder.id} />}
</div>
);
},
Source: ({ id }) => {
const info = useRepo(id)?.info;
if (!info) {
return <Loader2 className="w-4 h-4 animate-spin" />;
}
return <RepoLink link={info.repo_link} repo={info.repo} />;
},
Branch: ({ id }) => {
const branch = useRepo(id)?.info.branch;
return (
<div className="flex items-center gap-2">
<GitBranch className="w-4 h-4" />
{branch}
</div>
);
},
},
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 (
<div className="flex items-center gap-x-4 gap-y-2 flex-wrap">
{server?.id &&
(builder?.id ? (
<div className="pr-4 text-sm border-r">
<ResourceLink type="Server" id={server.id} />
</div>
) : (
<ResourceLink type="Server" id={server.id} />
))}
{builder?.id && <ResourceLink type="Builder" id={builder.id} />}
</div>
);
},
Repo: ({ id }) => {
const repo = useRepo(id)?.info.repo;
return (
<div className="flex items-center gap-2">
<FolderGit className="w-4 h-4" />
{repo}
</div>
);
},
Branch: ({ id }) => {
const branch = useRepo(id)?.info.branch;
return (
<div className="flex items-center gap-2">
<GitBranch className="w-4 h-4" />
{branch}
</div>
);
},
},
Actions: { BuildRepo, PullRepo, CloneRepo },
Page: {},

View File

@@ -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 }) => (
<SortableHeader column={column} title="Name" />
),
accessorKey: "name",
cell: ({ row }) => <ResourceLink type="Repo" id={row.original.id} />,
size: 200,
},
{
accessorKey: "info.repo",
header: ({ column }) => (
<SortableHeader column={column} title="Repo" />
),
accessorKey: "info.repo",
cell: ({ row }) => (
<RepoLink
repo={row.original.info.repo}
link={row.original.info.repo_link}
/>
),
size: 200,
},
{
accessorKey: "info.branch",
header: ({ column }) => (
<SortableHeader column={column} title="Branch" />
),
accessorKey: "info.branch",
size: 200,
},
{
accessorKey: "info.state",
header: ({ column }) => (
<SortableHeader column={column} title="State" />
),
accessorKey: "info.state",
cell: ({ row }) => <RepoComponents.State id={row.original.id} />,
size: 120,
},

View File

@@ -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 <StandardSource info={info} />;
},
LastSync: ({ id }) => {
const last_ts = useResourceSync(id)?.info.last_sync_ts;
return (
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
{last_ts ? fmt_date(new Date(last_ts)) : "Never"}
</div>
);
},
},
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 (
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
{last_ts ? fmt_date(new Date(last_ts)) : "Never"}
</div>
);
},
},
Actions: { RefreshSync, ExecuteSync, CommitSync },
Page: {},

View File

@@ -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 }) => (
<SortableHeader column={column} title="Name" />
),
accessorKey: "name",
cell: ({ row }) => (
<ResourceLink type="ResourceSync" id={row.original.id} />
),
size: 200,
},
{
accessorKey: "info.repo",
header: ({ column }) => (
<SortableHeader column={column} title="Repo" />
),
accessorKey: "info.repo",
cell: ({ row }) => <StandardSource info={row.original.info} />,
size: 200,
},
{
accessorKey: "info.branch",
header: ({ column }) => (
<SortableHeader column={column} title="Branch" />
),
accessorKey: "info.branch",
size: 200,
},
{
accessorKey: "info.state",
header: ({ column }) => (
<SortableHeader column={column} title="State" />
),
accessorKey: "info.state",
cell: ({ row }) => (
<ResourceSyncComponents.State id={row.original.id} />
),

View File

@@ -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 (
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-2 cursor-pointer">
{mismatch ? (
<AlertCircle
className={cn(
"w-4 h-4",
stroke_color_class_by_intention("Critical")
)}
/>
) : (
<CheckCircle2
className={cn(
"w-4 h-4",
stroke_color_class_by_intention("Good")
)}
/>
)}
{version ?? "Unknown"}
</div>
</TooltipTrigger>
<TooltipContent>
{mismatch ? (
<div>
Periphery version <span className="font-bold">mismatch</span>.
Expected <span className="font-bold">{core_version}</span>.
</div>
) : (
<div>
Periphery and Core version{" "}
<span className="font-bold">match</span>.
</div>
)}
</TooltipContent>
</Tooltip>
);
if (mismatch) {
}
return (
<div className={cn("flex items-center gap-2")}>
<Milestone className="w-4 h-4" />
{_version}
{version ?? "Unknown"}
</div>
);
},

View File

@@ -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;

View File

@@ -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 <StatusBadge text={state} intent={stack_state_intention(state)} />;
},
Info: {
Server: ({ id }) => {
const info = useStack(id)?.info;
const server = useServer(info?.server_id);
return server?.id ? (
<ResourceLink type="Server" id={server?.id} />
) : (
<div className="flex gap-2 items-center">
<Server className="w-4 h-4" />
<div>Unknown Server</div>
</div>
);
},
Source: ({ id }) => {
const info = useStack(id)?.info;
return <StandardSource info={info} />;
},
// Branch: ({ id }) => {
// 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">
// <GitBranch className="w-4 h-4" />
// {config.branch}
// </div>
// );
// },
Services: ({ id }) => {
const info = useStack(id)?.info;
return (
<div className="flex gap-1">
<div className="font-bold">{info?.services.length}</div>
<div>Service{(info?.services.length ?? 0 > 1) ? "s" : ""}</div>
</div>
);
},
},
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 (
<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" />
{config?.repo}
</div>
);
},
// Branch: ({ id }) => {
// 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">
// <GitBranch className="w-4 h-4" />
// {config.branch}
// </div>
// );
// },
Server: ({ id }) => {
const info = useStack(id)?.info;
const server = useServer(info?.server_id);
return server?.id ? (
<ResourceLink type="Server" id={server?.id} />
) : (
<div className="flex gap-2 items-center">
<Server className="w-4 h-4" />
<div>Unknown Server</div>
</div>
);
},
},
Actions: {
DeployStack,
PullStack,

View File

@@ -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 }) => (
<SortableHeader column={column} title="Name" />
),
accessorKey: "name",
cell: ({ row }) => {
return (
<div className="flex items-center justify-between gap-2">
@@ -40,27 +40,35 @@ export const StackTable = ({ stacks }: { stacks: Types.StackListItem[] }) => {
size: 200,
},
{
header: ({ column }) => (
<SortableHeader column={column} title="Server" />
),
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 }) => (
<SortableHeader column={column} title="Server" />
),
cell: ({ row }) => (
<ResourceLink type="Server" id={row.original.info.server_id} />
),
size: 200,
},
{
header: ({ column }) => (
<SortableHeader column={column} title="Source" />
),
accessorKey: "info.repo",
cell: ({ row }) => <StandardSource info={row.original.info} />,
size: 200,
},
{
accessorKey: "info.state",
header: ({ column }) => (

View File

@@ -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 = () => {
<Separator className="my-3" />
<p className="pl-4 pb-1 text-xs text-muted-foreground">Resources</p>
{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 = () => {
/>
);
})}
<Separator className="my-3" />
<p className="pl-4 pb-1 text-xs text-muted-foreground">Notifications</p>
@@ -68,8 +70,15 @@ export const Sidebar = () => {
to="/updates"
icon={<Bell className="w-4 h-4" />}
/>
<Separator className="my-3" />
<SidebarLink
label="Schedules"
to="/schedules"
icon={<CalendarDays className="w-4 h-4" />}
/>
<SidebarLink
label="Settings"
to="/settings"

View File

@@ -5,6 +5,7 @@ import {
Bell,
Box,
Boxes,
CalendarDays,
FileQuestion,
FolderTree,
Keyboard,
@@ -130,15 +131,17 @@ const MobileDropdown = () => {
? [<Box className="w-4 h-4" />, "Containers"]
: location.pathname === "/settings"
? [<Settings className="w-4 h-4" />, "Settings"]
: location.pathname === "/alerts"
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
: location.pathname === "/updates"
? [<Bell className="w-4 h-4" />, "Updates"]
: location.pathname.split("/")[1] === "user-groups"
? [<Users className="w-4 h-4" />, "User Groups"]
: location.pathname.split("/")[1] === "users"
? [<User className="w-4 h-4" />, "Users"]
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
: location.pathname === "/schedules"
? [<CalendarDays className="w-4 h-4" />, "Schedules"]
: location.pathname === "/alerts"
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
: location.pathname === "/updates"
? [<Bell className="w-4 h-4" />, "Updates"]
: location.pathname.split("/")[1] === "user-groups"
? [<Users className="w-4 h-4" />, "User Groups"]
: location.pathname.split("/")[1] === "users"
? [<User className="w-4 h-4" />, "Users"]
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
return (
<DropdownMenu>
@@ -202,6 +205,12 @@ const MobileDropdown = () => {
<DropdownMenuSeparator />
<DropdownLinkItem
label="Schedules"
icon={<CalendarDays className="w-4 h-4" />}
to="/schedules"
/>
<DropdownLinkItem
label="Settings"
icon={<Settings className="w-4 h-4" />}

View File

@@ -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 (
<ConfirmButton
variant={variant}
@@ -1079,3 +1086,18 @@ export const NotFound = ({ type }: { type: UsableResource | undefined }) => {
</div>
);
};
export const RepoLink = ({ repo, link }: { repo: string; link: string }) => {
return (
<a
target="_blank"
href={link}
className="text-sm cursor-pointer hover:underline"
>
<div className="flex items-center gap-2">
<FolderGit className="w-4 h-4" />
{repo}
</div>
</a>
);
};

View File

@@ -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:

View File

@@ -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"],

View File

@@ -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 }) =>

View File

@@ -29,17 +29,18 @@ export const homeViewAtom = atomWithStorage<HomeView>(
"Dashboard"
);
init_monaco().then(() =>
ReactDOM.createRoot(document.getElementById("root")!).render(
// <React.StrictMode>
<QueryClientProvider client={query_client}>
<WebsocketProvider>
<ThemeProvider>
<Router />
<Toaster />
</ThemeProvider>
</WebsocketProvider>
</QueryClientProvider>
// </React.StrictMode>
)
// Don't need to await this to render.
init_monaco();
ReactDOM.createRoot(document.getElementById("root")!).render(
// <React.StrictMode>
<QueryClientProvider client={query_client}>
<WebsocketProvider>
<ThemeProvider>
<Router />
<Toaster />
</ThemeProvider>
</WebsocketProvider>
</QueryClientProvider>
// </React.StrictMode>
);

View File

@@ -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 = () => {
</div>
</Page>
);
};
}

View File

@@ -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 = () => {
</div>
</Page>
);
};
}

View File

@@ -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 = () => {
</div>
</Page>
);
};
}
const TableSection = ({
type,

View File

@@ -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 = () => {
</Page>
</>
);
};
}
const ResourceRow = ({ type }: { type: UsableResource }) => {
const _recents = useUser().data?.recents?.[type]?.slice(0, 6);

View File

@@ -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 <Tree />;
}
};
}

View File

@@ -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 = () => {
>
<Section>
<div className="grid gap-6">
{servers?.map((server) => (
<Server key={server.id} id={server.id} />
))}
{servers?.map((server) => <Server key={server.id} id={server.id} />)}
</div>
</Section>
</Page>
);
};
}
const Server = ({ id }: { id: string }) => {
const [search] = useAtom(searchAtom);

View File

@@ -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 = () => {
</div>
</div>
);
};
}

View File

@@ -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 <ResourceInner type={type} id={id} />;
};
}
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 (
<div>
<div className="w-full flex items-center justify-between mb-12">
<Link to={"/" + usableResourcePath(type)}>
<Link
to={
"/" +
(SETTINGS_RESOURCES.includes(type)
? "settings"
: usableResourcePath(type))
}
>
<Button className="gap-2" variant="secondary">
<ChevronLeft className="w-4" />
Back
@@ -138,20 +145,24 @@ export const ResourceHeader = ({
{statusEntries.map(([key, Status]) => (
<Status key={key} id={id} />
))}
{links?.map((link) => (
<a
key={link}
target="_blank"
href={link}
className="flex gap-2 items-center pr-4 text-sm border-r cursor-pointer hover:underline last:pr-0 last:border-none"
>
<LinkIcon className="w-4" />
<div className="max-w-[150px] lg:max-w-[250px] overflow-hidden overflow-ellipsis">
{link}
</div>
</a>
))}
</div>
{links && links.length > 0 && (
<div className="flex items-center gap-x-4 gap-y-2 flex-wrap px-4 py-0">
{links?.map((link) => (
<a
key={link}
target="_blank"
href={link}
className="flex gap-2 items-center pr-4 text-sm border-r cursor-pointer hover:underline last:pr-0 last:border-none"
>
<LinkIcon className="w-4" />
<div className="max-w-[150px] lg:max-w-[250px] text-nowrap overflow-hidden overflow-ellipsis">
{link}
</div>
</a>
))}
</div>
)}
<div className="flex items-center gap-2 flex-wrap p-4 pt-0">
<p className="text-sm text-muted-foreground">Tags:</p>
<ResourceTags

View File

@@ -16,12 +16,14 @@ import { useState } from "react";
import { Search } from "lucide-react";
import { NotFound } from "@components/util";
import { Switch } from "@ui/switch";
import { UsableResource } from "@types";
export const Resources = () => {
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 = () => {
</div>
</Page>
);
};
}

View File

@@ -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 (
<Page
icon={<CalendarDays className="w-8" />}
title="Schedules"
subtitle={
<div className="text-muted-foreground">
See an overview of your scheduled tasks.
</div>
}
>
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-4 items-center justify-end">
<div className="flex items-center gap-4 flex-wrap">
<TagsFilter />
<div className="relative">
<Search className="w-4 absolute top-[50%] left-3 -translate-y-[50%] text-muted-foreground" />
<Input
value={search}
onChange={(e) => set(e.target.value)}
placeholder="search..."
className="pl-8 w-[200px] lg:w-[300px]"
/>
</div>
</div>
</div>
<DataTable
tableKey="schedules"
data={filtered}
columns={[
{
size: 200,
accessorKey: "name",
header: ({ column }) => (
<SortableHeader column={column} title="Target" />
),
cell: ({ row }) => (
<ResourceLink
type={row.original.target.type as UsableResource}
id={row.original.target.id}
/>
),
},
{
size: 200,
accessorKey: "schedule",
header: ({ column }) => (
<SortableHeader column={column} title="Schedule" />
),
},
{
size: 200,
accessorKey: "next_scheduled_run",
header: ({ column }) => (
<SortableHeader column={column} title="Next Run" />
),
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 }) => (
<SortableHeader column={column} title="Enabled" />
),
cell: ({ row: { original: schedule } }) => (
<ScheduleEnableSwitch
type={schedule.target.type as UsableResource}
id={schedule.target.id}
enabled={schedule.enabled}
/>
),
},
{
header: "Tags",
cell: ({ row }) => <TableTags tag_ids={row.original.tags} />,
},
]}
/>
</div>
</Page>
);
}
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 (
<Switch
checked={enabled}
onCheckedChange={(enabled) =>
mutate({ id, config: { schedule_enabled: enabled } })
}
disabled={!canWrite}
/>
);
};

View File

@@ -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 (
<ContainerPageInner id={id} container={decodeURIComponent(container)} />
);
};
}
const ContainerPageInner = ({
id,

View File

@@ -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 <div>This resource type does not have any images.</div>;
}
return <ImagePageInner id={id} image={decodeURIComponent(image)} />;
};
}
const ImagePageInner = ({
id,

View File

@@ -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 <div>This resource type does not have any networks.</div>;
}
return <NetworkPageInner id={id} network={decodeURIComponent(network)} />;
};
}
const NetworkPageInner = ({
id,

View File

@@ -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 <div>This resource type does not have any volumes.</div>;
}
return <VolumePageInner id={id} volume={decodeURIComponent(volume)} />;
};
}
const VolumePageInner = ({
id,

View File

@@ -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<SettingsView>("settings-view-v2", "Variables");
export const useSettingsView = () => useAtom<SettingsView>(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 = () => {
<TabsList className="justify-start w-fit">
<TabsTrigger value="Variables">Variables</TabsTrigger>
<TabsTrigger value="Tags">Tags</TabsTrigger>
<TabsTrigger value="Builders">Builders</TabsTrigger>
<TabsTrigger value="Alerters">Alerters</TabsTrigger>
{user?.admin && (
<TabsTrigger value="Providers">Providers</TabsTrigger>
)}
@@ -49,6 +52,12 @@ export const Settings = () => {
<TabsContent value="Tags">
<Tags />
</TabsContent>
<TabsContent value="Builders">
<Resources _type="Builder" />
</TabsContent>
<TabsContent value="Alerters">
<Resources _type="Alerter" />
</TabsContent>
{user?.admin && (
<TabsContent value="Providers">
<ProvidersPage />
@@ -65,4 +74,4 @@ export const Settings = () => {
</Tabs>
</Page>
);
};
}

View File

@@ -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 <div>This resource type does not have any services.</div>;
}
return <StackServicePageInner stack_id={id} service={service} />;
};
}
const StackServicePageInner = ({
stack_id,

View File

@@ -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 = () => {
</div>
</Page>
);
};
}
export const Updates = () => {
const type = useResourceParamType()!;

View File

@@ -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 = () => {
</div>
</Page>
);
};
}
const AddUserToGroup = ({ group_id }: { group_id: string }) => {
const inv = useInvalidate();

View File

@@ -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 = () => {
)}
</Page>
);
};
}
const ApiKeysTable = ({ user_id }: { user_id: string }) => {
const keys = useRead("ListApiKeysForServiceUser", { user: user_id }).data;

View File

@@ -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 (
<div className="w-full h-screen flex justify-center items-center">
<div className="flex flex-col gap-4 justify-center items-center">
@@ -20,4 +20,4 @@ export const UserDisabled = () => {
</div>
</div>
);
};
}

View File

@@ -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: <Layout />,
children: [
{ path: "", element: <Home /> },
{ path: "settings", element: <Settings /> },
{ path: "tree", element: <Tree /> },
{ path: "alerts", element: <AlertsPage /> },
{ path: "updates", element: <UpdatesPage /> },
{ path: "updates", element: <UpdatesPage /> },
{ path: "containers", element: <ContainersPage /> },
{ path: "resources", element: <AllResources /> },
{ path: "user-groups/:id", element: <UserGroupPage /> },
{
path: "users",
children: [{ path: ":id", element: <UserPage /> }],
},
{
path: ":type",
children: [
{ path: "", element: <Resources /> },
{ path: ":id", element: <Resource /> },
{
path: ":id/service/:service",
element: <StackServicePage />,
},
{
path: ":id/container/:container",
element: <ContainerPage />,
},
{
path: ":id/network/:network",
element: <NetworkPage />,
},
{
path: ":id/image/:image",
element: <ImagePage />,
},
{
path: ":id/volume/:volume",
element: <VolumePage />,
},
],
},
],
},
]);
// 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 <Login />;
if (!user.enabled) return <UserDisabled />;
return <RouterProvider router={ROUTER} />;
return (
<Suspense
fallback={
<div className="w-[100vw] h-[100vh] flex items-center justify-center">
<Loader2 className="w-16 h-16 animate-spin" />
</div>
}
>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="" element={<Home />} />
<Route path="settings" element={<Settings />} />
<Route path="tree" element={<Tree />} />
<Route path="alerts" element={<AlertsPage />} />
<Route path="updates" element={<UpdatesPage />} />
<Route path="containers" element={<ContainersPage />} />
<Route path="resources" element={<AllResources />} />
<Route path="schedules" element={<SchedulesPage />} />
<Route path="user-groups/:id" element={<UserGroupPage />} />
<Route path="users/:id" element={<UserPage />} />
<Route path=":type">
<Route path="" element={<Resources />} />
<Route path=":id" element={<Resource />} />
<Route
path=":id/service/:service"
element={<StackServicePage />}
/>
<Route
path=":id/container/:container"
element={<ContainerPage />}
/>
<Route path=":id/network/:network" element={<NetworkPage />} />
<Route path=":id/image/:image" element={<ImagePage />} />
<Route path=":id/volume/:volume" element={<VolumePage />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
</Suspense>
);
// return <RouterProvider router={ROUTER} />;
};

View File

@@ -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);

View File

@@ -54,10 +54,11 @@ where
T: Into<CloneArgs> + 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

View File

@@ -1,3 +1,5 @@
# Runfile | https://crates.io/crates/runnables-cli
[dev-frontend]
description = "starts the frontend in dev mode"
path = "frontend"