use std::time::Instant;
use rust_i18n::t;
use super::{
AccountInfo, AiAgentSummary, AppSummary, BalancerSummary, DashboardData, DatabaseSummary,
DedicatedServerSummary, DomainSummary, FinancesSummary, FirewallSummary, FloatingIpSummary,
ImageSummary, K8sSummary, KnowledgeBaseSummary, LogLevel, MailSummary, NetworkDriveSummary,
ProjectSummary, RegistrySummary, S3Summary, ServerSummary, SshKeySummary, VpcSummary
};
#[derive(Debug, Clone)]
pub enum DataSlice {
Account(AccountInfo),
Servers(Vec<ServerSummary>),
Databases(Vec<DatabaseSummary>),
S3(Vec<S3Summary>),
K8s(Vec<K8sSummary>),
ProjectsList(Vec<ProjectSummary>),
Projects(Vec<ProjectSummary>),
Balancers(Vec<BalancerSummary>),
Registries(Vec<RegistrySummary>),
Domains(Vec<DomainSummary>),
Firewalls(Vec<FirewallSummary>),
FloatingIps(Vec<FloatingIpSummary>),
Images(Vec<ImageSummary>),
NetworkDrives(Vec<NetworkDriveSummary>),
Vpcs(Vec<VpcSummary>),
DedicatedServers(Vec<DedicatedServerSummary>),
Mails(Vec<MailSummary>),
Apps(Vec<AppSummary>),
AiAgents(Vec<AiAgentSummary>),
KnowledgeBases(Vec<KnowledgeBaseSummary>),
SshKeys(Vec<SshKeySummary>),
Finances(FinancesSummary),
Error(String)
}
impl super::App {
pub fn force_refresh(&mut self) {
self.refresh_requested = true;
self.manual_refresh_spin = true;
self.log(LogLevel::Info, t!("app.log_manual_refresh").to_string());
}
#[must_use]
#[allow(dead_code)]
pub fn needs_refresh(&self) -> bool {
self.last_refresh.elapsed() >= self.refresh_interval
}
pub fn load_started(&mut self) {
self.cycle_load_errors.clear();
self.projects_pending = 2;
self.services_pending = 18;
}
#[allow(clippy::too_many_lines)]
pub fn apply_slice(&mut self, slice: DataSlice) {
match &slice {
DataSlice::Projects(_) | DataSlice::ProjectsList(_) => {
self.projects_pending = self.projects_pending.saturating_sub(1);
}
DataSlice::Account(_) | DataSlice::Finances(_) | DataSlice::Error(_) => {}
_ => self.services_pending = self.services_pending.saturating_sub(1)
}
match slice {
DataSlice::Account(mut info) => {
if info.balance.is_empty() {
info.balance = std::mem::take(&mut self.account.balance);
}
self.account = info;
}
DataSlice::Servers(v) => {
self.servers = v;
self.last_refresh = Instant::now();
}
DataSlice::Databases(v) => self.databases = v,
DataSlice::S3(v) => self.s3_storages = v,
DataSlice::K8s(v) => self.k8s_clusters = v,
DataSlice::ProjectsList(v) => {
let old_len = self.projects.len();
self.projects = v
.into_iter()
.map(|mut p| {
if let Some(prev) = self.projects.iter().find(|old| old.id == p.id) {
p.server_count = prev.server_count;
p.database_count = prev.database_count;
p.bucket_count = prev.bucket_count;
p.cluster_count = prev.cluster_count;
p.balancer_count = prev.balancer_count;
p.dedicated_count = prev.dedicated_count;
p.app_count = prev.app_count;
}
p
})
.collect();
self.anchor_nav_selection(old_len, self.projects.len());
}
DataSlice::Projects(v) => {
let old_len = self.projects.len();
self.projects = v
.into_iter()
.map(|mut p| {
if p.resource_count() == 0
&& let Some(prev) = self.projects.iter().find(|old| old.id == p.id)
&& prev.resource_count() > 0
{
p.server_count = prev.server_count;
p.database_count = prev.database_count;
p.bucket_count = prev.bucket_count;
p.cluster_count = prev.cluster_count;
p.balancer_count = prev.balancer_count;
p.dedicated_count = prev.dedicated_count;
p.app_count = prev.app_count;
}
p
})
.collect();
self.anchor_nav_selection(old_len, self.projects.len());
}
DataSlice::Balancers(v) => self.balancers = v,
DataSlice::Registries(v) => self.registries = v,
DataSlice::Domains(v) => self.domains = v,
DataSlice::Firewalls(v) => self.firewalls = v,
DataSlice::FloatingIps(v) => self.floating_ips = v,
DataSlice::Images(v) => self.images = v,
DataSlice::NetworkDrives(v) => self.network_drives = v,
DataSlice::Vpcs(v) => self.vpcs = v,
DataSlice::DedicatedServers(v) => self.dedicated_servers = v,
DataSlice::Mails(v) => self.mails = v,
DataSlice::Apps(v) => self.apps = v,
DataSlice::AiAgents(v) => self.ai_agents = v,
DataSlice::KnowledgeBases(v) => self.knowledge_bases = v,
DataSlice::SshKeys(v) => self.ssh_keys = v,
DataSlice::Finances(f) => {
self.account.balance = f.money(f.balance);
self.finances = Some(f);
}
DataSlice::Error(entry) => {
if !self.last_load_errors.contains(&entry) {
self.log(
LogLevel::Error,
t!("app.log_load_failed", entry => entry).to_string()
);
}
self.cycle_load_errors.push(entry);
return;
}
}
self.is_loading = false;
self.select_initial_tab();
self.clamp_selection();
}
const fn anchor_nav_selection(&mut self, old_len: usize, new_len: usize) {
if self.nav_selected >= old_len {
self.nav_selected = self.nav_selected + new_len - old_len;
} else if self.nav_selected >= new_len {
self.nav_selected = new_len.saturating_sub(1);
}
}
pub fn load_finished(&mut self) {
let recovered: Vec<String> = self
.last_load_errors
.iter()
.filter(|e| !self.cycle_load_errors.contains(e))
.cloned()
.collect();
for entry in recovered {
let name = entry.split(':').next().unwrap_or(&entry).to_string();
self.log(
LogLevel::Success,
t!("app.log_recovered", name => name).to_string()
);
}
self.last_load_errors = self.cycle_load_errors.clone();
self.is_loading = false;
self.projects_pending = 0;
self.services_pending = 0;
self.initial_cycle_done = true;
self.manual_refresh_spin = false;
self.snapshot_dirty = true;
if self.last_load_errors.is_empty() {
self.error_message = None;
self.status_message = Some(t!("app.load_ok").to_string());
} else {
self.error_message =
Some(t!("app.load_failures", n => self.last_load_errors.len()).to_string());
}
}
pub fn apply_data(&mut self, data: DashboardData) {
self.account = data.account;
self.servers = data.servers;
self.databases = data.databases;
self.s3_storages = data.s3_storages;
self.k8s_clusters = data.k8s_clusters;
self.projects = data.projects;
self.balancers = data.balancers;
self.registries = data.registries;
self.domains = data.domains;
self.firewalls = data.firewalls;
self.floating_ips = data.floating_ips;
self.images = data.images;
self.network_drives = data.network_drives;
self.vpcs = data.vpcs;
self.dedicated_servers = data.dedicated_servers;
self.mails = data.mails;
self.apps = data.apps;
self.ai_agents = data.ai_agents;
self.knowledge_bases = data.knowledge_bases;
self.ssh_keys = data.ssh_keys;
self.finances = data.finances;
if data.error_message.is_some() {
self.error_message = data.error_message;
} else {
self.error_message = None;
self.status_message = data.status_message;
}
if data.load_errors != self.last_load_errors {
let recovered: Vec<String> = self
.last_load_errors
.iter()
.filter(|r| !data.load_errors.contains(r))
.cloned()
.collect();
for entry in &data.load_errors {
self.log(
LogLevel::Error,
t!("app.log_load_failed", entry => entry).to_string()
);
}
for entry in recovered {
let name = entry.split(':').next().unwrap_or(&entry);
self.log(
LogLevel::Success,
t!("app.log_recovered", name => name).to_string()
);
}
self.last_load_errors = data.load_errors;
}
self.select_initial_tab();
self.clamp_selection();
self.is_loading = false;
self.last_refresh = Instant::now();
}
}