mirror of
https://github.com/moghtech/komodo.git
synced 2025-12-05 19:17:36 -06:00
* start 1.18.5 * prevent empty additional permission check (ie for new resources) * dev-2 * bump rust to 1.88 * tweaks * repo based stack commit happens from core repo cache rather than on server to simplify * clippy auto fix * clippy lints periphery * clippy fix komodo_client * dev-3 * emphasize ferret version pinning * bump svi with PR fix * dev-4 * webhook disabled early return * Fix missing alert types for whitelist * add "ScheduleRun" * fix status cache not cleaning on resource delete * dev-5 * forgot to pipe through poll in previous refactor * refetch given in ms * fix configure build extra args * reorder resource sync config * Implement ability to run actions at startup (#664) * Implement ability to run actions at startup * run post-startup actions after server is listening * startup use action query * fmt * Fix Google Login enabled message (#668) - it was showing "Github Login" instead of "Google Login" * Allow CIDR ranges in Allowed IPs (#666) * Allow CIDR ranges in Allowed IPs * Catch mixed IPv4/IPv6 mappings that are probably intended to match * forgiving vec * dev-6 * forgiving vec log. allowed ips docs * server stats UI: move current disk breakdown above charts * searchable container stats, toggle collaple container / disk sections * Add Clear repo cache method * fix execute usage docs * Komodo managed env-file should take precedence in all cases (ie come last in env file list) * tag include unused flag for future use * combine users page search * util backup / restore * refactor backup/restore duplication * cleanup restore * core image include util binary * dev-7 * back to LinesCodec * dev-8 * clean up * clean up logs * rename to komodo-util * dev-9 * enable_fance_toml * dev-10 enable fancy toml * add user agent to oidc requests (#701) Co-authored-by: eleith <online-github@eleith.com> * fmt * use database library * clippy lint * consolidate and standardize cli * dev-11 * dev-12 implement backup using cli * dev-13 logs * command variant fields need to be #[arg] * tweak cli * gen client * fix terminal reconnect issue * rename cli to `km` * tweaks for the cli logs * wait for enter on --yes empty println * fix --yes * dev-15 * bump deps * update croner to latest, use static parser * dev-16 * cli execute polls updates until complete before logging * remove repo cache mount * cli nice * /backup -> /backups * dev-17 config loading preserves CONFIG_PATHS precedence * update dockerfile default docker cli config keywords * dev-18 * support .kmignore * add ignores log * Implement automatic backup pruning, default 14 backups before prune * db copy / restore uses idempotent upsert * cli update variable - "km set var VAR value" * improve cli initial logs * time the executions * implement update for most resources * dev 20 * add update page * dev 21 support cli update link * dev-22 test the deploy * dev-23 use indexmap * install-cli.py * Frontend mobile fixes (#714) * Allow ResourcePageHeader items to wrap * Allow CardHeader items to wrap * Increase z-index of sticky TableHeader, fixes #690 * Remove fixed widths from ActionButton, let them flex more to fit more layouts * Make Section scroll overflow * Remove grid class from Tabs, seems to prevent them from overflowing at small sizes * deploy 1.18.5-dev-24 * auto version increment and deploy * cli: profiles support aliases and merge on top of Default (root) config * fix page set titles * rust 1.89 and improve config logs * skip serializing for proper merge * fix clippy lints re 1.89 * remove layouts overflow-x-scroll * deploy 1.18.5-dev-25 * 1.89 docker images not ready yet * km cfg -a (print all profiles) * include commit variables * skip serializing profiles when empty * skip serialize default db / log configs * km cfg --debug print mode * correct defaults for CLI and only can pass restore folder from cli arg * some more skip serialization * db restore / copy index optional * add runfile command aliases * remove second schedule updating loop, can causes some schedules to be missed * deploy 1.18.5-dev-26 * add log when target db indexing disabled * cli: user password reset, update user super admin * Add manual network interface configuration for multi-NIC Docker environments (#719) * Add iproute2 to debian-debs * feat: Add manual network interface configuration for multi-NIC support Complete implementation of manual interface configuration: - Add internet_interface config option - Implement manual gateway routing - Add NET_ADMIN capability requirement - Clean up codebase changes * fix: Update internet interface handling for multi-NIC support * refactor: Enhance error messages and logging in networking module * refactor: Simplify interface argument handling and improve logging in network configuration and cleanup * refactor(network): simplify startup integration and improve error handling - Move config access and error handling into network::configure_internet_gateway() - Simplify startup.rs to single function call without parameters - Remove redundant check_network_privileges() function - Improve error handling by checking actual command output instead of pre-validation - Better separation of concerns between startup and network modules Addresses feedback from PR discussion: https://github.com/moghtech/komodo/pull/719#discussion_r2261542921 * fix(config): update default internet interface setting Addresses feedback from PR discussion: https://github.com/moghtech/komodo/pull/719#discussion_r2261552279 * fix(config): remove custom default for internet interface in CoreConfig * move mod.rs -> network.rs Addresses feedback from PR discussion: https://github.com/moghtech/komodo/pull/719#discussion_r2261558332 * add internet interface example * docs(build-images): document multi-platform builds with Docker Buildx (#721) * docs(build-images): add multi-platform buildx guide to builders.md * docs(build-images): add multi-platform buildx guide and clarify platform selection in Komodo UI Extra Args field * move to 1.19.0 * core support reading from multiple config files * config support yaml * deploy 1.19.0-dev-1 * deploy 1.19.0-dev-2 * add default komodo cli config * better config merge with base * no need to panic if empty config paths * improve km --help * prog on cli docs * tweak cli docs * tweak doc * split the runfile commands * update docsite deps * km ps initial * km ls * list resource apis * km con inspect * deploy 1.19.0-dev-3 * fix: need serde default * dev-4 fix container parsing issue * tweak * use include-based file finding for much faster discovery * just move to standard config dir .config/komodo/komodo.cli.* * update fe w/ new contianer info minimal serialization * add links to table names * deploy 1.19.0-dev-5 * links in tables * backend for Action arguments * deploy 1.19.0-dev-6 * deploy 1.19.0-dev-7 * deploy 1.19.0-dev-8 * no space at front of KeyValue default args * webhook branch / body optional * The incoming arguments * deploy 1.19.0-dev-9 * con -> cn * add config -> cf alias * .kmignore * .peripheryinclude * outdated * optional links, configurable table format * table_format -> table_borders * get types * include docsite in yarn install * update runnables command in docs * tweak * improve km ls only show important stuff * Add BackupCoreDatabase * deploy 1.19.0-dev-10 * backup command needs "--yes" * deploy 1.19.0-dev-11 * update rustc 1.89.0 * cli tweak * try chef * Fix chef (after dependencies) * try other compile command * fix * fix comment * cleanup stats page * ensure database backup procedure * UI allow configure Backup Core Database in Procedures * procedure description * deploy 1.19.0-dev-12 * deploy 1.19.0-dev-13 * GlobalAutoUpdate * deploy 1.19.0-dev-14 * default tags and global auto update procedure * deploy 1.19.0-dev-15 * trim the default procedure descriptions * deploy 1.19.0-dev-16 * in "system" theme, also poll for updates to the theme based on time. * Add next run to Action / Procedure column * km ls support filter by templates * fix procedure toml serialization when params = {} * deploy 1.19.0-dev-17 * KOMODO_INIT_ADMIN_USERNAME * KOMODO_FIRST_SERVER_NAME * add server.config.external_address for use with links * deploy 1.19.0-dev-18 * improve auto prune * fix system theme auto update * deploy 1.19.0-dev-19 * rename auth/CreateLocalUser -> SignUpLocalUser. Add write/CreateLocalUser for in-ui initialization. * deploy 1.19.0-dev-20 * UI can handle multiple active logins * deploy 1.19.0-dev-21 * fix * add logout function * fix oauth redirect * fix multi user exchange token function * default external address * just Add * style account switcher * backup and restore docs * rework docsite file / sidebar structure, start auto update docs * auto update docs * tweak * fix doc links * only pull / update running stacks / deployments images * deploy 1.19.0-dev-22 * deploy 1.19.0-dev-23 * fix #737 * community docs * add BackupCoreDatabase link to docs * update ferret v2 update guide using komodo-cli * fix data table headers overlapping topbar * don't alert when deploying * CommitSync returns Update * deploy 1.19.0-dev-24 * trim the decoded branch * action uses file contents deserializer * deploy 1.19.0-dev-25 * remove Toml from action args format * clarify External Address purpose * Fix podman compatibility in `get_container_stats` (#739) * Add podman compability for querying stats Podman and docker stats differ in results in significant ways but this filter change they will output the same stats * syntax fix * feat(dashboard): display CPU, memory, and disk usage on server cards (#729) * feat: mini-stats-card: Expose Server CPU , Memory, Disk Usage to Dashboard View * comment: resolved * Feat: fix overflow card , DRY stats-mini, add unreachable mini stats * lint: fix * deploy 1.19.0-dev-26 * 1.19.0 * linux, macos container install * cli main config --------- Co-authored-by: Brian Bradley <brian.bradley.p@gmail.com> Co-authored-by: Daniel <daniel.barabasa@gmail.com> Co-authored-by: eleith <eleith@users.noreply.github.com> Co-authored-by: eleith <online-github@eleith.com> Co-authored-by: Sam Edwards <sam@samedwards.ca> Co-authored-by: Marcel Pfennig <82059270+MP-Tool@users.noreply.github.com> Co-authored-by: itsmesid <693151+arevindh@users.noreply.github.com> Co-authored-by: mbecker20 <max@mogh.tech> Co-authored-by: Rhyn <Rhyn@users.noreply.github.com> Co-authored-by: Anh Nguyen <tuananh131001@gmail.com>
1172 lines
32 KiB
Rust
1172 lines
32 KiB
Rust
use std::{cmp::Ordering, collections::HashMap};
|
|
|
|
use comfy_table::{Attribute, Cell, Color};
|
|
use futures_util::{FutureExt, try_join};
|
|
use komodo_client::{
|
|
KomodoClient,
|
|
api::read::{
|
|
ListActions, ListAlerters, ListBuilders, ListBuilds,
|
|
ListDeployments, ListProcedures, ListRepos, ListResourceSyncs,
|
|
ListSchedules, ListServers, ListStacks, ListTags,
|
|
},
|
|
entities::{
|
|
ResourceTargetVariant,
|
|
action::{ActionListItem, ActionListItemInfo, ActionState},
|
|
alerter::{AlerterListItem, AlerterListItemInfo},
|
|
build::{BuildListItem, BuildListItemInfo, BuildState},
|
|
builder::{BuilderListItem, BuilderListItemInfo},
|
|
config::cli::args::{
|
|
self,
|
|
list::{ListCommand, ResourceFilters},
|
|
},
|
|
deployment::{
|
|
DeploymentListItem, DeploymentListItemInfo, DeploymentState,
|
|
},
|
|
procedure::{
|
|
ProcedureListItem, ProcedureListItemInfo, ProcedureState,
|
|
},
|
|
repo::{RepoListItem, RepoListItemInfo, RepoState},
|
|
resource::{ResourceListItem, ResourceQuery},
|
|
resource_link,
|
|
schedule::Schedule,
|
|
server::{ServerListItem, ServerListItemInfo, ServerState},
|
|
stack::{StackListItem, StackListItemInfo, StackState},
|
|
sync::{
|
|
ResourceSyncListItem, ResourceSyncListItemInfo,
|
|
ResourceSyncState,
|
|
},
|
|
},
|
|
};
|
|
use serde::Serialize;
|
|
|
|
use crate::{
|
|
command::{
|
|
PrintTable, format_timetamp, matches_wildcards, parse_wildcards,
|
|
print_items,
|
|
},
|
|
config::cli_config,
|
|
};
|
|
|
|
pub async fn handle(list: &args::list::List) -> anyhow::Result<()> {
|
|
match &list.command {
|
|
None => list_all(list).await,
|
|
Some(ListCommand::Servers(filters)) => {
|
|
list_resources::<ServerListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Stacks(filters)) => {
|
|
list_resources::<StackListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Deployments(filters)) => {
|
|
list_resources::<DeploymentListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Builds(filters)) => {
|
|
list_resources::<BuildListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Repos(filters)) => {
|
|
list_resources::<RepoListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Procedures(filters)) => {
|
|
list_resources::<ProcedureListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Actions(filters)) => {
|
|
list_resources::<ActionListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Syncs(filters)) => {
|
|
list_resources::<ResourceSyncListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Builders(filters)) => {
|
|
list_resources::<BuilderListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Alerters(filters)) => {
|
|
list_resources::<AlerterListItem>(filters, false).await
|
|
}
|
|
Some(ListCommand::Schedules(filters)) => {
|
|
list_schedules(filters).await
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Includes all resources besides builds and alerters.
|
|
async fn list_all(list: &args::list::List) -> anyhow::Result<()> {
|
|
let filters: ResourceFilters = list.clone().into();
|
|
let client = super::komodo_client().await?;
|
|
let (
|
|
tags,
|
|
mut servers,
|
|
mut stacks,
|
|
mut deployments,
|
|
mut builds,
|
|
mut repos,
|
|
mut procedures,
|
|
mut actions,
|
|
mut syncs,
|
|
) = try_join!(
|
|
client.read(ListTags::default()).map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|t| (t.id, t.name))
|
|
.collect::<HashMap<_, _>>())),
|
|
ServerListItem::list(client, &filters, true),
|
|
StackListItem::list(client, &filters, true),
|
|
DeploymentListItem::list(client, &filters, true),
|
|
BuildListItem::list(client, &filters, true),
|
|
RepoListItem::list(client, &filters, true),
|
|
ProcedureListItem::list(client, &filters, true),
|
|
ActionListItem::list(client, &filters, true),
|
|
ResourceSyncListItem::list(client, &filters, true),
|
|
)?;
|
|
|
|
if !servers.is_empty() {
|
|
fix_tags(&mut servers, &tags);
|
|
print_items(servers, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !stacks.is_empty() {
|
|
fix_tags(&mut stacks, &tags);
|
|
print_items(stacks, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !deployments.is_empty() {
|
|
fix_tags(&mut deployments, &tags);
|
|
print_items(deployments, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !builds.is_empty() {
|
|
fix_tags(&mut builds, &tags);
|
|
print_items(builds, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !repos.is_empty() {
|
|
fix_tags(&mut repos, &tags);
|
|
print_items(repos, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !procedures.is_empty() {
|
|
fix_tags(&mut procedures, &tags);
|
|
print_items(procedures, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !actions.is_empty() {
|
|
fix_tags(&mut actions, &tags);
|
|
print_items(actions, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
if !syncs.is_empty() {
|
|
fix_tags(&mut syncs, &tags);
|
|
print_items(syncs, filters.format, list.links)?;
|
|
println!();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn list_resources<T>(
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<()>
|
|
where
|
|
T: ListResources,
|
|
ResourceListItem<T::Info>: PrintTable + Serialize,
|
|
{
|
|
let client = crate::command::komodo_client().await?;
|
|
let (mut resources, tags) = tokio::try_join!(
|
|
T::list(client, filters, minimal),
|
|
client.read(ListTags::default()).map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|t| (t.id, t.name))
|
|
.collect::<HashMap<_, _>>()))
|
|
)?;
|
|
fix_tags(&mut resources, &tags);
|
|
if !resources.is_empty() {
|
|
print_items(resources, filters.format, filters.links)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn list_schedules(
|
|
filters: &ResourceFilters,
|
|
) -> anyhow::Result<()> {
|
|
let client = crate::command::komodo_client().await?;
|
|
let (mut schedules, tags) = tokio::try_join!(
|
|
client
|
|
.read(ListSchedules {
|
|
tags: filters.tags.clone(),
|
|
tag_behavior: Default::default(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.filter(|s| s.next_scheduled_run.is_some())
|
|
.collect::<Vec<_>>())),
|
|
client.read(ListTags::default()).map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|t| (t.id, t.name))
|
|
.collect::<HashMap<_, _>>()))
|
|
)?;
|
|
schedules.iter_mut().for_each(|resource| {
|
|
resource.tags.iter_mut().for_each(|id| {
|
|
let Some(name) = tags.get(id) else {
|
|
*id = String::new();
|
|
return;
|
|
};
|
|
id.clone_from(name);
|
|
});
|
|
});
|
|
schedules.sort_by(|a, b| {
|
|
match (a.next_scheduled_run, b.next_scheduled_run) {
|
|
(Some(_), None) => return Ordering::Less,
|
|
(None, Some(_)) => return Ordering::Greater,
|
|
(Some(a), Some(b)) => return a.cmp(&b),
|
|
(None, None) => {}
|
|
}
|
|
a.name.cmp(&b.name).then(a.enabled.cmp(&b.enabled))
|
|
});
|
|
if !schedules.is_empty() {
|
|
print_items(schedules, filters.format, filters.links)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn fix_tags<T>(
|
|
resources: &mut Vec<ResourceListItem<T>>,
|
|
tags: &HashMap<String, String>,
|
|
) {
|
|
resources.iter_mut().for_each(|resource| {
|
|
resource.tags.iter_mut().for_each(|id| {
|
|
let Some(name) = tags.get(id) else {
|
|
*id = String::new();
|
|
return;
|
|
};
|
|
id.clone_from(name);
|
|
});
|
|
});
|
|
}
|
|
|
|
trait ListResources: Sized
|
|
where
|
|
ResourceListItem<Self::Info>: PrintTable,
|
|
{
|
|
type Info;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
// For use with root `km ls`
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<ResourceListItem<Self::Info>>>;
|
|
}
|
|
|
|
// LIST
|
|
|
|
impl ListResources for ServerListItem {
|
|
type Info = ServerListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
_minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let servers = client
|
|
.read(ListServers {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?;
|
|
let names = parse_wildcards(&filters.names);
|
|
let server_wildcards = parse_wildcards(&filters.servers);
|
|
let mut servers = servers
|
|
.into_iter()
|
|
.filter(|server| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
!matches!(server.info.state, ServerState::Ok)
|
|
} else if filters.in_progress {
|
|
false
|
|
} else {
|
|
matches!(server.info.state, ServerState::Ok)
|
|
};
|
|
let name_items = &[server.name.as_str()];
|
|
state_check
|
|
&& matches_wildcards(&names, name_items)
|
|
&& matches_wildcards(&server_wildcards, name_items)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
servers.sort_by(|a, b| {
|
|
a.info.state.cmp(&b.info.state).then(a.name.cmp(&b.name))
|
|
});
|
|
Ok(servers)
|
|
}
|
|
}
|
|
|
|
impl ListResources for StackListItem {
|
|
type Info = StackListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
_minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let (servers, mut stacks) = tokio::try_join!(
|
|
client
|
|
.read(ListServers {
|
|
query: ResourceQuery::builder().build(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|s| (s.id.clone(), s))
|
|
.collect::<HashMap<_, _>>())),
|
|
client.read(ListStacks {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
)?;
|
|
stacks.iter_mut().for_each(|stack| {
|
|
if stack.info.server_id.is_empty() {
|
|
return;
|
|
}
|
|
let Some(server) = servers.get(&stack.info.server_id) else {
|
|
return;
|
|
};
|
|
stack.info.server_id.clone_from(&server.name);
|
|
});
|
|
let names = parse_wildcards(&filters.names);
|
|
let servers = parse_wildcards(&filters.servers);
|
|
let mut stacks = stacks
|
|
.into_iter()
|
|
.filter(|stack| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
!matches!(
|
|
stack.info.state,
|
|
StackState::Running | StackState::Deploying
|
|
)
|
|
} else if filters.in_progress {
|
|
matches!(stack.info.state, StackState::Deploying)
|
|
} else {
|
|
matches!(
|
|
stack.info.state,
|
|
StackState::Running | StackState::Deploying
|
|
)
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[stack.name.as_str()])
|
|
&& matches_wildcards(
|
|
&servers,
|
|
&[stack.info.server_id.as_str()],
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
stacks.sort_by(|a, b| {
|
|
a.info
|
|
.state
|
|
.cmp(&b.info.state)
|
|
.then(a.name.cmp(&b.name))
|
|
.then(a.info.server_id.cmp(&b.info.server_id))
|
|
});
|
|
Ok(stacks)
|
|
}
|
|
}
|
|
|
|
impl ListResources for DeploymentListItem {
|
|
type Info = DeploymentListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
_minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let (servers, mut deployments) = tokio::try_join!(
|
|
client
|
|
.read(ListServers {
|
|
query: ResourceQuery::builder().build(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|s| (s.id.clone(), s))
|
|
.collect::<HashMap<_, _>>())),
|
|
client.read(ListDeployments {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
)?;
|
|
deployments.iter_mut().for_each(|deployment| {
|
|
if deployment.info.server_id.is_empty() {
|
|
return;
|
|
}
|
|
let Some(server) = servers.get(&deployment.info.server_id)
|
|
else {
|
|
return;
|
|
};
|
|
deployment.info.server_id.clone_from(&server.name);
|
|
});
|
|
let names = parse_wildcards(&filters.names);
|
|
let servers = parse_wildcards(&filters.servers);
|
|
let mut deployments = deployments
|
|
.into_iter()
|
|
.filter(|deployment| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
!matches!(
|
|
deployment.info.state,
|
|
DeploymentState::Running | DeploymentState::Deploying
|
|
)
|
|
} else if filters.in_progress {
|
|
matches!(deployment.info.state, DeploymentState::Deploying)
|
|
} else {
|
|
matches!(
|
|
deployment.info.state,
|
|
DeploymentState::Running | DeploymentState::Deploying
|
|
)
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[deployment.name.as_str()])
|
|
&& matches_wildcards(
|
|
&servers,
|
|
&[deployment.info.server_id.as_str()],
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
deployments.sort_by(|a, b| {
|
|
a.info
|
|
.state
|
|
.cmp(&b.info.state)
|
|
.then(a.name.cmp(&b.name))
|
|
.then(a.info.server_id.cmp(&b.info.server_id))
|
|
});
|
|
Ok(deployments)
|
|
}
|
|
}
|
|
|
|
impl ListResources for BuildListItem {
|
|
type Info = BuildListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let (builders, mut builds) = tokio::try_join!(
|
|
client
|
|
.read(ListBuilders {
|
|
query: ResourceQuery::builder().build(),
|
|
})
|
|
.map(|res| res.map(|res| res
|
|
.into_iter()
|
|
.map(|s| (s.id.clone(), s))
|
|
.collect::<HashMap<_, _>>())),
|
|
client.read(ListBuilds {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
)?;
|
|
builds.iter_mut().for_each(|build| {
|
|
if build.info.builder_id.is_empty() {
|
|
return;
|
|
}
|
|
let Some(builder) = builders.get(&build.info.builder_id) else {
|
|
return;
|
|
};
|
|
build.info.builder_id.clone_from(&builder.name);
|
|
});
|
|
let names = parse_wildcards(&filters.names);
|
|
let builders = parse_wildcards(&filters.builders);
|
|
let mut builds = builds
|
|
.into_iter()
|
|
.filter(|build| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
build.info.state,
|
|
BuildState::Failed | BuildState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(build.info.state, BuildState::Building)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[build.name.as_str()])
|
|
&& matches_wildcards(
|
|
&builders,
|
|
&[build.info.builder_id.as_str()],
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
builds.sort_by(|a, b| {
|
|
a.name
|
|
.cmp(&b.name)
|
|
.then(a.info.builder_id.cmp(&b.info.builder_id))
|
|
.then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(builds)
|
|
}
|
|
}
|
|
|
|
impl ListResources for RepoListItem {
|
|
type Info = RepoListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut repos = client
|
|
.read(ListRepos {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|repo| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
repo.info.state,
|
|
RepoState::Failed | RepoState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(
|
|
repo.info.state,
|
|
RepoState::Building | RepoState::Cloning
|
|
)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[repo.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
repos.sort_by(|a, b| {
|
|
a.name
|
|
.cmp(&b.name)
|
|
.then(a.info.server_id.cmp(&b.info.server_id))
|
|
.then(a.info.builder_id.cmp(&b.info.builder_id))
|
|
});
|
|
Ok(repos)
|
|
}
|
|
}
|
|
|
|
impl ListResources for ProcedureListItem {
|
|
type Info = ProcedureListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut procedures = client
|
|
.read(ListProcedures {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|procedure| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
procedure.info.state,
|
|
ProcedureState::Failed | ProcedureState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(procedure.info.state, ProcedureState::Running)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[procedure.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
procedures.sort_by(|a, b| {
|
|
match (a.info.next_scheduled_run, b.info.next_scheduled_run) {
|
|
(Some(_), None) => return Ordering::Less,
|
|
(None, Some(_)) => return Ordering::Greater,
|
|
(Some(a), Some(b)) => return a.cmp(&b),
|
|
(None, None) => {}
|
|
}
|
|
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(procedures)
|
|
}
|
|
}
|
|
|
|
impl ListResources for ActionListItem {
|
|
type Info = ActionListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut actions = client
|
|
.read(ListActions {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|action| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
action.info.state,
|
|
ActionState::Failed | ActionState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(action.info.state, ActionState::Running)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[action.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
actions.sort_by(|a, b| {
|
|
match (a.info.next_scheduled_run, b.info.next_scheduled_run) {
|
|
(Some(_), None) => return Ordering::Less,
|
|
(None, Some(_)) => return Ordering::Greater,
|
|
(Some(a), Some(b)) => return a.cmp(&b),
|
|
(None, None) => {}
|
|
}
|
|
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(actions)
|
|
}
|
|
}
|
|
|
|
impl ListResources for ResourceSyncListItem {
|
|
type Info = ResourceSyncListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut syncs = client
|
|
.read(ListResourceSyncs {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|sync| {
|
|
let state_check = if filters.all {
|
|
true
|
|
} else if filters.down {
|
|
matches!(
|
|
sync.info.state,
|
|
ResourceSyncState::Failed | ResourceSyncState::Unknown
|
|
)
|
|
} else if minimal || filters.in_progress {
|
|
matches!(
|
|
sync.info.state,
|
|
ResourceSyncState::Syncing | ResourceSyncState::Pending
|
|
)
|
|
} else {
|
|
true
|
|
};
|
|
state_check
|
|
&& matches_wildcards(&names, &[sync.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
syncs.sort_by(|a, b| {
|
|
a.name.cmp(&b.name).then(a.info.state.cmp(&b.info.state))
|
|
});
|
|
Ok(syncs)
|
|
}
|
|
}
|
|
|
|
impl ListResources for BuilderListItem {
|
|
type Info = BuilderListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut builders = client
|
|
.read(ListBuilders {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|builder| {
|
|
(!minimal || filters.all)
|
|
&& matches_wildcards(&names, &[builder.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
builders.sort_by(|a, b| {
|
|
a.name
|
|
.cmp(&b.name)
|
|
.then(a.info.builder_type.cmp(&b.info.builder_type))
|
|
});
|
|
Ok(builders)
|
|
}
|
|
}
|
|
|
|
impl ListResources for AlerterListItem {
|
|
type Info = AlerterListItemInfo;
|
|
async fn list(
|
|
client: &KomodoClient,
|
|
filters: &ResourceFilters,
|
|
minimal: bool,
|
|
) -> anyhow::Result<Vec<Self>> {
|
|
let names = parse_wildcards(&filters.names);
|
|
let mut syncs = client
|
|
.read(ListAlerters {
|
|
query: ResourceQuery::builder()
|
|
.tags(filters.tags.clone())
|
|
// .tag_behavior(TagQueryBehavior::Any)
|
|
.templates(filters.templates)
|
|
.build(),
|
|
})
|
|
.await?
|
|
.into_iter()
|
|
.filter(|sync| {
|
|
(!minimal || filters.all)
|
|
&& matches_wildcards(&names, &[sync.name.as_str()])
|
|
})
|
|
.collect::<Vec<_>>();
|
|
syncs.sort_by(|a, b| {
|
|
a.info
|
|
.enabled
|
|
.cmp(&b.info.enabled)
|
|
.then(a.name.cmp(&b.name))
|
|
.then(a.info.endpoint_type.cmp(&b.info.endpoint_type))
|
|
});
|
|
Ok(syncs)
|
|
}
|
|
}
|
|
|
|
// TABLE
|
|
|
|
impl PrintTable for ResourceListItem<ServerListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Server", "State", "Address", "Tags", "Link"]
|
|
} else {
|
|
&["Server", "State", "Address", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<Cell> {
|
|
let color = match self.info.state {
|
|
ServerState::Ok => Color::Green,
|
|
ServerState::NotOk => Color::Red,
|
|
ServerState::Disabled => Color::Blue,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.address),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Server,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<StackListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Stack", "State", "Server", "Tags", "Link"]
|
|
} else {
|
|
&["Stack", "State", "Server", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
StackState::Down => Color::Blue,
|
|
StackState::Running => Color::Green,
|
|
StackState::Deploying => Color::DarkYellow,
|
|
StackState::Paused => Color::DarkYellow,
|
|
StackState::Unknown => Color::Magenta,
|
|
_ => Color::Red,
|
|
};
|
|
// let source = if self.info.files_on_host {
|
|
// "On Host"
|
|
// } else if !self.info.repo.is_empty() {
|
|
// self.info.repo_link.as_str()
|
|
// } else {
|
|
// "UI Defined"
|
|
// };
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.server_id),
|
|
// Cell::new(source),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Stack,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<DeploymentListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Deployment", "State", "Server", "Tags", "Link"]
|
|
} else {
|
|
&["Deployment", "State", "Server", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
DeploymentState::NotDeployed => Color::Blue,
|
|
DeploymentState::Running => Color::Green,
|
|
DeploymentState::Deploying => Color::DarkYellow,
|
|
DeploymentState::Paused => Color::DarkYellow,
|
|
DeploymentState::Unknown => Color::Magenta,
|
|
_ => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.server_id),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Deployment,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<BuildListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Build", "State", "Builder", "Tags", "Link"]
|
|
} else {
|
|
&["Build", "State", "Builder", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
BuildState::Ok => Color::Green,
|
|
BuildState::Building => Color::DarkYellow,
|
|
BuildState::Unknown => Color::Magenta,
|
|
BuildState::Failed => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.builder_id),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Build,
|
|
&self.id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<RepoListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Repo", "State", "Link", "Tags", "Link"]
|
|
} else {
|
|
&["Repo", "State", "Link", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
RepoState::Ok => Color::Green,
|
|
RepoState::Building
|
|
| RepoState::Cloning
|
|
| RepoState::Pulling => Color::DarkYellow,
|
|
RepoState::Unknown => Color::Magenta,
|
|
RepoState::Failed => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.repo_link),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Repo,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<ProcedureListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Procedure", "State", "Next Run", "Tags", "Link"]
|
|
} else {
|
|
&["Procedure", "State", "Next Run", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
ProcedureState::Ok => Color::Green,
|
|
ProcedureState::Running => Color::DarkYellow,
|
|
ProcedureState::Unknown => Color::Magenta,
|
|
ProcedureState::Failed => Color::Red,
|
|
};
|
|
let next_run = if let Some(ts) = self.info.next_scheduled_run {
|
|
Cell::new(
|
|
format_timetamp(ts)
|
|
.unwrap_or(String::from("Invalid next ts")),
|
|
)
|
|
.add_attribute(Attribute::Bold)
|
|
} else {
|
|
Cell::new(String::from("None"))
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
next_run,
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Procedure,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<ActionListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Action", "State", "Next Run", "Tags", "Link"]
|
|
} else {
|
|
&["Action", "State", "Next Run", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
ActionState::Ok => Color::Green,
|
|
ActionState::Running => Color::DarkYellow,
|
|
ActionState::Unknown => Color::Magenta,
|
|
ActionState::Failed => Color::Red,
|
|
};
|
|
let next_run = if let Some(ts) = self.info.next_scheduled_run {
|
|
Cell::new(
|
|
format_timetamp(ts)
|
|
.unwrap_or(String::from("Invalid next ts")),
|
|
)
|
|
.add_attribute(Attribute::Bold)
|
|
} else {
|
|
Cell::new(String::from("None"))
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
next_run,
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Action,
|
|
&self.id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<ResourceSyncListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Sync", "State", "Tags", "Link"]
|
|
} else {
|
|
&["Sync", "State", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let color = match self.info.state {
|
|
ResourceSyncState::Ok => Color::Green,
|
|
ResourceSyncState::Pending | ResourceSyncState::Syncing => {
|
|
Color::DarkYellow
|
|
}
|
|
ResourceSyncState::Unknown => Color::Magenta,
|
|
ResourceSyncState::Failed => Color::Red,
|
|
};
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.state.to_string())
|
|
.fg(color)
|
|
.add_attribute(Attribute::Bold),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::ResourceSync,
|
|
&self.id,
|
|
)))
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<BuilderListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Builder", "Type", "Tags", "Link"]
|
|
} else {
|
|
&["Builder", "Type", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.builder_type),
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Builder,
|
|
&self.id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|
|
|
|
impl PrintTable for ResourceListItem<AlerterListItemInfo> {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Alerter", "Type", "Enabled", "Tags", "Link"]
|
|
} else {
|
|
&["Alerter", "Type", "Enabled", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let mut row = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.info.endpoint_type),
|
|
if self.info.enabled {
|
|
Cell::new(self.info.enabled.to_string()).fg(Color::Green)
|
|
} else {
|
|
Cell::new(self.info.enabled.to_string()).fg(Color::Red)
|
|
},
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
row.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
ResourceTargetVariant::Alerter,
|
|
&self.id,
|
|
)));
|
|
}
|
|
row
|
|
}
|
|
}
|
|
|
|
impl PrintTable for Schedule {
|
|
fn header(links: bool) -> &'static [&'static str] {
|
|
if links {
|
|
&["Name", "Type", "Next Run", "Tags", "Link"]
|
|
} else {
|
|
&["Name", "Type", "Next Run", "Tags"]
|
|
}
|
|
}
|
|
fn row(self, links: bool) -> Vec<comfy_table::Cell> {
|
|
let next_run = if let Some(ts) = self.next_scheduled_run {
|
|
Cell::new(
|
|
format_timetamp(ts)
|
|
.unwrap_or(String::from("Invalid next ts")),
|
|
)
|
|
.add_attribute(Attribute::Bold)
|
|
} else {
|
|
Cell::new(String::from("None"))
|
|
};
|
|
let (resource_type, id) = self.target.extract_variant_id();
|
|
let mut res = vec![
|
|
Cell::new(self.name).add_attribute(Attribute::Bold),
|
|
Cell::new(self.target.extract_variant_id().0),
|
|
next_run,
|
|
Cell::new(self.tags.join(", ")),
|
|
];
|
|
if links {
|
|
res.push(Cell::new(resource_link(
|
|
&cli_config().host,
|
|
resource_type,
|
|
id,
|
|
)));
|
|
}
|
|
res
|
|
}
|
|
}
|