improv the sync

This commit is contained in:
mbecker20
2024-06-08 00:50:30 -07:00
parent 6aa801b705
commit cad1ee123e
15 changed files with 232 additions and 136 deletions

8
Cargo.lock generated
View File

@@ -2552,18 +2552,18 @@ checksum = "ffa94c2e5674923c67d7f3dfce1279507b191e10eb064881b46ed3e1256e5ca6"
[[package]]
name = "partial_derive2"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7b915bd76bc306b7ae6f1b5d99b0434e498ab979ecbd5df119db8a00dab972"
checksum = "6b2bd06fda40521c285c8dfc5f6b90208d586328a295e83332b6166c2b2a4241"
dependencies = [
"partial_derive2_derive",
]
[[package]]
name = "partial_derive2_derive"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddaac49c0e65bcb207999d2514b10b43d5f2ec2d0fb47b9d875c9a10536a294e"
checksum = "3a506f66d52e40b2385d7b9f776fd5243d6cff16ba79147f859aa4e27d2d27cc"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -30,7 +30,7 @@ derive_empty_traits = "0.1.0"
merge_config_files = "0.1.5"
termination_signal = "0.1.3"
async_timing_util = "0.1.14"
partial_derive2 = "0.4.2"
partial_derive2 = "0.4.3"
derive_variants = "1.0.0"
mongo_indexed = "0.3.0"
resolver_api = "1.1.0"

View File

@@ -62,6 +62,10 @@ pub trait ResourceSync: Sized {
resource: ResourceToml<Self::PartialConfig>,
) -> anyhow::Result<()>;
/// Apply any changes to incoming toml partial config
/// before it is diffed against existing config
fn validate_partial_config(_config: &mut Self::PartialConfig) {}
/// Diffs the declared toml (partial) against the full existing config.
/// Removes all fields from toml (partial) that haven't changed.
fn get_diff(
@@ -69,6 +73,10 @@ pub trait ResourceSync: Sized {
update: Self::PartialConfig,
) -> anyhow::Result<Self::ConfigDiff>;
/// Apply any changes to computed config diff
/// before logging
fn validate_diff(_diff: &mut Self::ConfigDiff) {}
/// Deletes the target resource
async fn delete(id_or_name: String) -> anyhow::Result<()>;
@@ -188,11 +196,15 @@ pub fn get_updates<Resource: ResourceSync>(
let config: Resource::Config = resource.config.into();
resource.config = config.into();
let diff = Resource::get_diff(
Resource::validate_partial_config(&mut resource.config);
let mut diff = Resource::get_diff(
original.config.clone(),
resource.config,
)?;
Resource::validate_diff(&mut diff);
let original_tags = original
.tags
.iter()

View File

@@ -78,6 +78,14 @@ impl ResourceSync for Build {
Ok(original.partial_diff(update))
}
fn validate_diff(diff: &mut Self::ConfigDiff) {
if let Some((_, to)) = &diff.version {
if to.is_none() {
diff.version = None;
}
}
}
async fn delete(id: String) -> anyhow::Result<()> {
monitor_client().write(DeleteBuild { id }).await?;
Ok(())

View File

@@ -30,11 +30,12 @@ use monitor_client::{
},
};
use mungos::find::find_collect;
use partial_derive2::PartialDiff;
use resolver_api::Resolve;
use crate::{
helpers::query::get_user_user_group_ids,
resource,
resource::{self, MonitorResource},
state::{db_client, State},
};
@@ -166,7 +167,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
PermissionLevel::Read,
)
.await?;
res.alerters.push(convert_resource(alerter, &names.tags))
res
.alerters
.push(convert_resource::<Alerter>(alerter, &names.tags))
}
ResourceTarget::ResourceSync(id) => {
let sync = resource::get_check_permissions::<ResourceSync>(
@@ -175,7 +178,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
PermissionLevel::Read,
)
.await?;
res.resource_syncs.push(convert_resource(sync, &names.tags))
res
.resource_syncs
.push(convert_resource::<ResourceSync>(sync, &names.tags))
}
ResourceTarget::ServerTemplate(id) => {
let template = resource::get_check_permissions::<
@@ -184,9 +189,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
&id, &user, PermissionLevel::Read
)
.await?;
res
.server_templates
.push(convert_resource(template, &names.tags))
res.server_templates.push(
convert_resource::<ServerTemplate>(template, &names.tags),
)
}
ResourceTarget::Server(id) => {
let server = resource::get_check_permissions::<Server>(
@@ -195,7 +200,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
PermissionLevel::Read,
)
.await?;
res.servers.push(convert_resource(server, &names.tags))
res
.servers
.push(convert_resource::<Server>(server, &names.tags))
}
ResourceTarget::Builder(id) => {
let mut builder =
@@ -211,7 +218,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
names.servers.get(&id).unwrap_or(&String::new()),
)
}
res.builders.push(convert_resource(builder, &names.tags))
res
.builders
.push(convert_resource::<Builder>(builder, &names.tags))
}
ResourceTarget::Build(id) => {
let mut build = resource::get_check_permissions::<Build>(
@@ -227,7 +236,9 @@ impl Resolve<ExportResourcesToToml, User> for State {
.get(&build.config.builder_id)
.unwrap_or(&String::new()),
);
res.builds.push(convert_resource(build, &names.tags))
res
.builds
.push(convert_resource::<Build>(build, &names.tags))
}
ResourceTarget::Deployment(id) => {
let mut deployment = resource::get_check_permissions::<
@@ -251,9 +262,10 @@ impl Resolve<ExportResourcesToToml, User> for State {
names.builds.get(build_id).unwrap_or(&String::new()),
);
}
res
.deployments
.push(convert_resource(deployment, &names.tags))
res.deployments.push(convert_resource::<Deployment>(
deployment,
&names.tags,
))
}
ResourceTarget::Repo(id) => {
let mut repo = resource::get_check_permissions::<Repo>(
@@ -269,7 +281,7 @@ impl Resolve<ExportResourcesToToml, User> for State {
.get(&repo.config.server_id)
.unwrap_or(&String::new()),
);
res.repos.push(convert_resource(repo, &names.tags))
res.repos.push(convert_resource::<Repo>(repo, &names.tags))
}
ResourceTarget::Procedure(id) => {
add_procedure(&id, &mut res, &user, &names)
@@ -381,7 +393,7 @@ async fn add_procedure(
res
.procedures
.push(convert_resource(procedure, &names.tags));
.push(convert_resource::<Procedure>(procedure, &names.tags));
Ok(())
}
@@ -500,13 +512,13 @@ async fn add_user_groups(
Ok(())
}
fn convert_resource<Config, Info: Default, PartialConfig>(
resource: Resource<Config, Info>,
fn convert_resource<R: MonitorResource>(
resource: Resource<R::Config, R::Info>,
tag_names: &HashMap<String, String>,
) -> ResourceToml<PartialConfig>
where
Config: Into<PartialConfig>,
{
) -> ResourceToml<R::PartialConfig> {
// This makes sure all non-necessary (defaulted) fields don't make it into final toml
let partial: R::PartialConfig = resource.config.into();
let config = R::Config::default().minimize_partial(partial);
ResourceToml {
name: resource.name,
tags: resource
@@ -515,6 +527,6 @@ where
.filter_map(|t| tag_names.get(t).cloned())
.collect(),
description: resource.description,
config: resource.config.into(),
config,
}
}

View File

@@ -12,7 +12,7 @@ use monitor_client::{
repo::Repo,
server::Server,
server_template::ServerTemplate,
sync::{PendingUpdates, ResourceSync},
sync::{PendingSyncUpdates, ResourceSync},
user::User,
},
};
@@ -105,7 +105,7 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
let all_resources = AllResourcesById::load().await?;
let id_to_tags = get_id_to_tags(None).await?;
let pending = PendingUpdates {
let pending = PendingSyncUpdates {
hash,
message,
server_updates: get_updates_for_view::<Server>(

View File

@@ -13,6 +13,7 @@ use monitor_client::{
repo::Repo,
server::Server,
server_template::ServerTemplate,
sync::SyncUpdate,
tag::Tag,
toml::ResourceToml,
update::{Log, ResourceTarget},
@@ -44,6 +45,10 @@ pub struct ToUpdateItem<T> {
pub trait ResourceSync: MonitorResource + Sized {
fn resource_target(id: String) -> ResourceTarget;
/// Apply any changes to incoming toml partial config
/// before it is diffed against existing config
fn validate_partial_config(_config: &mut Self::PartialConfig) {}
/// Diffs the declared toml (partial) against the full existing config.
/// Removes all fields from toml (partial) that haven't changed.
fn get_diff(
@@ -52,6 +57,10 @@ pub trait ResourceSync: MonitorResource + Sized {
resources: &AllResourcesById,
) -> anyhow::Result<Self::ConfigDiff>;
/// Apply any changes to computed config diff
/// before logging
fn validate_diff(_diff: &mut Self::ConfigDiff) {}
async fn run_updates(
to_create: ToCreate<Self::PartialConfig>,
to_update: ToUpdate<Self::PartialConfig>,
@@ -194,7 +203,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
delete: bool,
all_resources: &AllResourcesById,
id_to_tags: &HashMap<String, Tag>,
) -> anyhow::Result<Option<String>> {
) -> anyhow::Result<Option<SyncUpdate>> {
let map = find_collect(Resource::coll().await, None, None)
.await
.context("failed to get resources from db")?
@@ -202,20 +211,21 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
.map(|r| (r.name.clone(), r))
.collect::<HashMap<_, _>>();
let mut any_change = false;
let mut update = SyncUpdate {
log: format!("{} Updates", Resource::resource_type()),
..Default::default()
};
let mut to_delete = Vec::<String>::new();
if delete {
for resource in map.values() {
if !resources.iter().any(|r| r.name == resource.name) {
any_change = true;
update.to_delete += 1;
to_delete.push(resource.name.clone())
}
}
}
let mut log = format!("{} Updates", Resource::resource_type());
for mut resource in resources {
match map.get(&resource.name) {
Some(original) => {
@@ -224,12 +234,16 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
let config: Resource::Config = resource.config.into();
resource.config = config.into();
let diff = Resource::get_diff(
Resource::validate_partial_config(&mut resource.config);
let mut diff = Resource::get_diff(
original.config.clone(),
resource.config,
all_resources,
)?;
Resource::validate_diff(&mut diff);
let original_tags = original
.tags
.iter()
@@ -245,9 +259,9 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
continue;
}
any_change = true;
update.to_update += 1;
log.push_str(&format!(
update.log.push_str(&format!(
"\n\n{}: {}: '{}'\n-------------------",
colored("UPDATE", "blue"),
Resource::resource_type(),
@@ -287,12 +301,12 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
)
},
));
log.push('\n');
log.push_str(&lines.join("\n-------------------\n"));
update.log.push('\n');
update.log.push_str(&lines.join("\n-------------------\n"));
}
None => {
any_change = true;
log.push_str(&format!(
update.to_create += 1;
update.log.push_str(&format!(
"\n\n{}: {}: {}\n{}: {}\n{}: {:?}\n{}: {}",
colored("CREATE", "green"),
Resource::resource_type(),
@@ -310,7 +324,7 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
}
for name in to_delete {
log.push_str(&format!(
update.log.push_str(&format!(
"\n\n{}: {}: '{}'\n-------------------",
colored("DELETE", "red"),
Resource::resource_type(),
@@ -318,7 +332,11 @@ pub async fn get_updates_for_view<Resource: ResourceSync>(
));
}
Ok(any_change.then_some(log))
let any_change = update.to_create > 0
|| update.to_update > 0
|| update.to_delete > 0;
Ok(any_change.then_some(update))
}
/// Gets all the resources to update. For use in sync execution.
@@ -355,12 +373,16 @@ pub async fn get_updates_for_execution<Resource: ResourceSync>(
let config: Resource::Config = resource.config.into();
resource.config = config.into();
let diff = Resource::get_diff(
Resource::validate_partial_config(&mut resource.config);
let mut diff = Resource::get_diff(
original.config.clone(),
resource.config,
all_resources,
)?;
Resource::validate_diff(&mut diff);
let original_tags = original
.tags
.iter()

View File

@@ -63,6 +63,14 @@ impl ResourceSync for Build {
Ok(original.partial_diff(update))
}
fn validate_diff(diff: &mut Self::ConfigDiff) {
if let Some((_, to)) = &diff.version {
if to.is_none() {
diff.version = None;
}
}
}
}
impl ResourceSync for Deployment {

View File

@@ -11,6 +11,7 @@ use monitor_client::{
},
entities::{
permission::UserTarget,
sync::SyncUpdate,
toml::{PermissionToml, UserGroupToml},
update::{Log, ResourceTarget},
user::sync_user,
@@ -38,7 +39,7 @@ pub async fn get_updates_for_view(
user_groups: Vec<UserGroupToml>,
delete: bool,
all_resources: &AllResourcesById,
) -> anyhow::Result<Option<String>> {
) -> anyhow::Result<Option<SyncUpdate>> {
let map = find_collect(&db_client().await.user_groups, None, None)
.await
.context("failed to query db for UserGroups")?
@@ -46,32 +47,22 @@ pub async fn get_updates_for_view(
.map(|ug| (ug.name.clone(), ug))
.collect::<HashMap<_, _>>();
let mut update = SyncUpdate {
log: String::from("User Group Updates"),
..Default::default()
};
let mut to_delete = Vec::<String>::new();
if delete {
for user_group in map.values() {
if !user_groups.iter().any(|ug| ug.name == user_group.name) {
update.to_delete += 1;
to_delete.push(user_group.name.clone());
}
}
}
let mut log = String::from("User Group Updates");
if user_groups.is_empty() {
if to_delete.is_empty() {
return Ok(None);
}
for name in &to_delete {
log.push_str(&format!(
"\n\n{}: user group: '{}'\n-------------------",
colored("DELETE", "red"),
bold(name),
));
}
return Ok(Some(log));
}
let id_to_user = find_collect(&db_client().await.users, None, None)
.await
.context("failed to query db for Users")?
@@ -83,7 +74,8 @@ pub async fn get_updates_for_view(
let original = match map.get(&user_group.name).cloned() {
Some(original) => original,
None => {
log.push_str(&format!(
update.to_create += 1;
update.log.push_str(&format!(
"\n\n{}: user group: {}\n{}: {:?}\n{}: {:?}",
colored("CREATE", "green"),
colored(&user_group.name, "green"),
@@ -199,9 +191,10 @@ pub async fn get_updates_for_view(
let update_permissions =
user_group.permissions != original_permissions;
// only push update after failed diff
// only add log after diff detected
if update_users || update_permissions {
log.push_str(&format!(
update.to_update += 1;
update.log.push_str(&format!(
"\n\n{}: user group: '{}'\n-------------------",
colored("UPDATE", "blue"),
bold(&user_group.name),
@@ -269,20 +262,20 @@ pub async fn get_updates_for_view(
muted("adding"),
))
}
log.push('\n');
log.push_str(&lines.join("\n-------------------\n"));
update.log.push('\n');
update.log.push_str(&lines.join("\n-------------------\n"));
}
}
for name in &to_delete {
log.push_str(&format!(
update.log.push_str(&format!(
"\n\n{}: user group: '{}'\n-------------------",
colored("DELETE", "red"),
bold(name),
));
}
Ok(Some(log))
Ok(Some(update))
}
pub async fn get_updates_for_execution(

View File

@@ -6,7 +6,10 @@ use monitor_client::{
CreateVariable, DeleteVariable, UpdateVariableDescription,
UpdateVariableValue,
},
entities::{update::Log, user::sync_user, variable::Variable},
entities::{
sync::SyncUpdate, update::Log, user::sync_user,
variable::Variable,
},
};
use mungos::find::find_collect;
use resolver_api::Resolve;
@@ -24,7 +27,7 @@ pub struct ToUpdateItem {
pub async fn get_updates_for_view(
variables: Vec<Variable>,
delete: bool,
) -> anyhow::Result<Option<String>> {
) -> anyhow::Result<Option<SyncUpdate>> {
let map = find_collect(&db_client().await.variables, None, None)
.await
.context("failed to query db for variables")?
@@ -32,32 +35,22 @@ pub async fn get_updates_for_view(
.map(|v| (v.name.clone(), v))
.collect::<HashMap<_, _>>();
let mut update = SyncUpdate {
log: String::from("Variable Updates"),
..Default::default()
};
let mut to_delete = Vec::<String>::new();
if delete {
for variable in map.values() {
if !variables.iter().any(|v| v.name == variable.name) {
update.to_delete += 1;
to_delete.push(variable.name.clone());
}
}
}
let mut log = String::from("Variable Updates");
if variables.is_empty() {
if to_delete.is_empty() {
return Ok(None);
}
for name in &to_delete {
log.push_str(&format!(
"\n\n{}: variable: '{}'\n-------------------",
colored("DELETE", "red"),
bold(name),
));
}
return Ok(Some(log));
}
for variable in variables {
match map.get(&variable.name) {
Some(original) => {
@@ -70,7 +63,8 @@ pub async fn get_updates_for_view(
if !item.update_value && !item.update_description {
continue;
}
log.push_str(&format!(
update.to_update += 1;
update.log.push_str(&format!(
"\n\n{}: variable: '{}'\n-------------------",
colored("UPDATE", "blue"),
bold(&item.variable.name),
@@ -100,11 +94,12 @@ pub async fn get_updates_for_view(
))
}
log.push('\n');
log.push_str(&lines.join("\n-------------------\n"));
update.log.push('\n');
update.log.push_str(&lines.join("\n-------------------\n"));
}
None => {
log.push_str(&format!(
update.to_create += 1;
update.log.push_str(&format!(
"\n\n{}: variable: {}\n{}: {}\n{}: {}",
colored("CREATE", "green"),
colored(&variable.name, "green"),
@@ -118,14 +113,14 @@ pub async fn get_updates_for_view(
}
for name in &to_delete {
log.push_str(&format!(
update.log.push_str(&format!(
"\n\n{}: variable: '{}'\n-------------------",
colored("DELETE", "red"),
bold(name),
));
}
Ok(Some(log))
Ok(Some(update))
}
pub async fn get_updates_for_execution(

View File

@@ -64,6 +64,7 @@ pub use repo::{
pub trait MonitorResource {
type ListItem: Serialize + Send;
type Config: Clone
+ Default
+ Send
+ Sync
+ Unpin

View File

@@ -62,38 +62,51 @@ pub struct ResourceSyncInfo {
/// Commit message of last applied sync
pub last_sync_message: String,
/// Readable logs of pending updates
pub pending: PendingUpdates,
pub pending: PendingSyncUpdates,
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PendingUpdates {
pub struct SyncUpdate {
/// Resources to create
pub to_create: i32,
/// Resources to update
pub to_update: i32,
/// Resources to delete
pub to_delete: i32,
/// A readable log of all the changes to be applied
pub log: String,
}
#[typeshare]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PendingSyncUpdates {
/// The commit hash which produced these pending updates
pub hash: String,
/// The commit message which produced these pending updates
pub message: String,
/// Readable log of any pending server updates
pub server_updates: Option<String>,
pub server_updates: Option<SyncUpdate>,
/// Readable log of any pending deployment updates
pub deployment_updates: Option<String>,
pub deployment_updates: Option<SyncUpdate>,
/// Readable log of any pending build updates
pub build_updates: Option<String>,
pub build_updates: Option<SyncUpdate>,
/// Readable log of any pending repo updates
pub repo_updates: Option<String>,
pub repo_updates: Option<SyncUpdate>,
/// Readable log of any pending procedure updates
pub procedure_updates: Option<String>,
pub procedure_updates: Option<SyncUpdate>,
/// Readable log of any pending alerter updates
pub alerter_updates: Option<String>,
pub alerter_updates: Option<SyncUpdate>,
/// Readable log of any pending builder updates
pub builder_updates: Option<String>,
pub builder_updates: Option<SyncUpdate>,
/// Readable log of any pending server template updates
pub server_template_updates: Option<String>,
pub server_template_updates: Option<SyncUpdate>,
/// Readable log of any pending resource sync updates
pub resource_sync_updates: Option<String>,
pub resource_sync_updates: Option<SyncUpdate>,
/// Readable log of any pending variable updates
pub variable_updates: Option<String>,
pub variable_updates: Option<SyncUpdate>,
/// Readable log of any pending user group updates
pub user_group_updates: Option<String>,
pub user_group_updates: Option<SyncUpdate>,
}
#[typeshare(serialized_as = "Partial<ResourceSyncConfig>")]

View File

@@ -1174,33 +1174,44 @@ export interface ResourceSyncConfig {
webhook_enabled: boolean;
}
export interface PendingUpdates {
export interface SyncUpdate {
/** Resources to create */
to_create: number;
/** Resources to update */
to_update: number;
/** Resources to delete */
to_delete: number;
/** A readable log of all the changes to be applied */
log: string;
}
export interface PendingSyncUpdates {
/** The commit hash which produced these pending updates */
hash: string;
/** The commit message which produced these pending updates */
message: string;
/** Readable log of any pending server updates */
server_updates?: string;
server_updates?: SyncUpdate;
/** Readable log of any pending deployment updates */
deployment_updates?: string;
deployment_updates?: SyncUpdate;
/** Readable log of any pending build updates */
build_updates?: string;
build_updates?: SyncUpdate;
/** Readable log of any pending repo updates */
repo_updates?: string;
repo_updates?: SyncUpdate;
/** Readable log of any pending procedure updates */
procedure_updates?: string;
procedure_updates?: SyncUpdate;
/** Readable log of any pending alerter updates */
alerter_updates?: string;
alerter_updates?: SyncUpdate;
/** Readable log of any pending builder updates */
builder_updates?: string;
builder_updates?: SyncUpdate;
/** Readable log of any pending server template updates */
server_template_updates?: string;
server_template_updates?: SyncUpdate;
/** Readable log of any pending resource sync updates */
resource_sync_updates?: string;
resource_sync_updates?: SyncUpdate;
/** Readable log of any pending variable updates */
variable_updates?: string;
variable_updates?: SyncUpdate;
/** Readable log of any pending user group updates */
user_group_updates?: string;
user_group_updates?: SyncUpdate;
}
export interface ResourceSyncInfo {
@@ -1211,7 +1222,7 @@ export interface ResourceSyncInfo {
/** Commit message of last applied sync */
last_sync_message: string;
/** Readable logs of pending updates */
pending: PendingUpdates;
pending: PendingSyncUpdates;
}
export type ResourceSync = Resource<ResourceSyncConfig, ResourceSyncInfo>;

View File

@@ -46,7 +46,11 @@ const PendingOrConfig = ({ id }: { id: string }) => {
const tabsList = (
<TabsList className="justify-start w-fit">
<TabsTrigger value="Pending" className="w-[110px]" disabled={true}>
<TabsTrigger
value="Pending"
className="w-[110px]"
disabled={pendingDisabled}
>
Pending
</TabsTrigger>
<TabsTrigger value="Config" className="w-[110px]">
@@ -65,7 +69,7 @@ const PendingOrConfig = ({ id }: { id: string }) => {
<PendingView
key={type}
type={type}
log={sync?.info?.pending?.[key]}
pending={sync?.info?.pending?.[key]}
/>
))}
</Section>
@@ -76,22 +80,39 @@ const PendingOrConfig = ({ id }: { id: string }) => {
const PendingView = ({
type,
log,
pending,
}: {
type: string;
log: string | undefined;
pending: Types.SyncUpdate | undefined;
}) => {
if (!log) return;
if (!pending) return;
return (
<Card>
<CardHeader className="flex-col">
<CardHeader className="flex items-center justify-between gap-4">
<CardTitle>{type} Updates</CardTitle>
<div className="flex gap-4 items-center">
{pending.to_create && (
<div className="flex gap-2 items-center">
To Create: {pending.to_create}
</div>
)}
{pending.to_update && (
<div className="flex gap-2 items-center">
To Update: {pending.to_update}
</div>
)}
{pending.to_delete && (
<div className="flex gap-2 items-center">
To Delete: {pending.to_delete}
</div>
)}
</div>
</CardHeader>
<CardContent>
<pre
dangerouslySetInnerHTML={{
__html: sanitizeOnlySpan(log),
__html: sanitizeOnlySpan(pending.log),
}}
/>
</CardContent>

View File

@@ -180,16 +180,16 @@ export const sync_no_changes = (sync: Types.ResourceSync) => {
const pending = sync.info?.pending;
if (!pending) return false;
return (
!pending.server_updates?.length &&
!pending.deployment_updates?.length &&
!pending.build_updates?.length &&
!pending.repo_updates?.length &&
!pending.procedure_updates?.length &&
!pending.alerter_updates?.length &&
!pending.builder_updates?.length &&
!pending.server_template_updates?.length &&
!pending.resource_sync_updates?.length &&
!pending.variable_updates?.length &&
!pending.user_group_updates?.length
!pending.server_updates &&
!pending.deployment_updates &&
!pending.build_updates &&
!pending.repo_updates &&
!pending.procedure_updates &&
!pending.alerter_updates &&
!pending.builder_updates &&
!pending.server_template_updates &&
!pending.resource_sync_updates &&
!pending.variable_updates &&
!pending.user_group_updates
);
};