use std::time::Instant;
use crate::app::{self, App};
pub(crate) fn handle_tick(
app: &mut App,
anim: &mut crate::animation::AnimationState,
vault_signing: bool,
last_config_check: &mut Instant,
) {
app.tick_status();
app.tick_toast();
let provider_syncing = !app.providers.syncing().is_empty();
let tunnels_animating =
matches!(app.top_page, crate::app::TopPage::Tunnels) && !app.tunnels.active().is_empty();
if anim.has_checking_hosts(app)
|| vault_signing
|| provider_syncing
|| anim.has_reachable_hosts(app)
|| tunnels_animating
{
anim.tick_spinner();
}
if vault_signing {
if let Some(status) = app.status_center.status_mut() {
if status.sticky && !status.is_error() {
let frame = crate::animation::SPINNER_FRAMES
[anim.spinner_tick as usize % crate::animation::SPINNER_FRAMES.len()];
if let Some(updated) = crate::replace_spinner_frame(&status.text, frame) {
status.text = updated;
}
}
}
}
if provider_syncing {
if let Some(status) = app.status_center.status_mut() {
let frame = crate::animation::SPINNER_FRAMES
[anim.spinner_tick as usize % crate::animation::SPINNER_FRAMES.len()];
if let Some(updated) = crate::replace_spinner_frame(&status.text, frame) {
status.text = updated;
status.created_at = std::time::Instant::now();
}
}
}
if last_config_check.elapsed() >= std::time::Duration::from_secs(4) {
app.check_config_changed();
app.check_keys_changed();
*last_config_check = Instant::now();
}
let exited = app.poll_tunnels();
for (_alias, msg, is_error) in exited {
if is_error {
app.notify_background_error(msg);
} else {
app.notify_background(msg);
}
}
}
pub(crate) fn handle_ping_result(
app: &mut App,
alias: String,
rtt_ms: Option<u32>,
generation: u64,
) {
if generation == app.ping.generation() {
let status = app::classify_ping(rtt_ms, app.ping.slow_threshold_ms());
let now = Instant::now();
log::debug!(
"ping-result: {} → {:?} (rtt={:?}ms, gen={})",
alias,
status,
rtt_ms,
generation
);
app.ping.insert_status(alias.clone(), status.clone());
app.ping.record_check(alias.clone(), now);
app::propagate_ping_to_dependents(
app.hosts_state.list(),
app.ping.status_map_mut(),
&alias,
&status,
);
let mut propagated = 0usize;
for h in app.hosts_state.list() {
if h.proxy_jump == alias {
app.ping.record_check(h.alias.clone(), now);
propagated += 1;
}
}
if propagated > 0 {
log::debug!(
"ping-result: propagated bastion {} status+timestamp to {} dependent(s)",
alias,
propagated
);
}
if app.ping.filter_down_only() {
app.apply_filter();
}
if app.hosts_state.sort_mode() == app::SortMode::Status {
app.apply_sort();
}
if !app.ping.status_is_empty()
&& app
.ping
.status_map()
.values()
.all(|s| !matches!(s, app::PingStatus::Checking))
{
app.ping.set_checked_at(Some(Instant::now()));
}
}
}
pub(crate) fn handle_update_available(app: &mut App, version: String, headline: Option<String>) {
app.update.announce(version, headline);
}