botserver/src/console/status_panel.rs
Rodrigo Rodriguez 5ea171d126
Some checks failed
BotServer CI / build (push) Failing after 1m34s
Refactor: Split large files into modular subdirectories
Split 20+ files over 1000 lines into focused subdirectories for better
maintainability and code organization. All changes maintain backward
compatibility through re-export wrappers.

Major splits:
- attendance/llm_assist.rs (2074→7 modules)
- basic/keywords/face_api.rs → face_api/ (7 modules)
- basic/keywords/file_operations.rs → file_ops/ (8 modules)
- basic/keywords/hear_talk.rs → hearing/ (6 modules)
- channels/wechat.rs → wechat/ (10 modules)
- channels/youtube.rs → youtube/ (5 modules)
- contacts/mod.rs → contacts_api/ (6 modules)
- core/bootstrap/mod.rs → bootstrap/ (5 modules)
- core/shared/admin.rs → admin_*.rs (5 modules)
- designer/canvas.rs → canvas_api/ (6 modules)
- designer/mod.rs → designer_api/ (6 modules)
- docs/handlers.rs → handlers_api/ (11 modules)
- drive/mod.rs → drive_handlers.rs, drive_types.rs
- learn/mod.rs → types.rs
- main.rs → main_module/ (7 modules)
- meet/webinar.rs → webinar_api/ (8 modules)
- paper/mod.rs → (10 modules)
- security/auth.rs → auth_api/ (7 modules)
- security/passkey.rs → (4 modules)
- sources/mod.rs → sources_api/ (5 modules)
- tasks/mod.rs → task_api/ (5 modules)

Stats: 38,040 deletions, 1,315 additions across 318 files

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-12 21:09:30 +00:00

293 lines
11 KiB
Rust

use crate::core::config::ConfigManager;
#[cfg(feature = "nvidia")]
use crate::nvidia::get_system_metrics;
use crate::security::command_guard::SafeCommand;
use crate::core::shared::models::schema::bots::dsl::*;
use crate::core::shared::state::AppState;
use diesel::prelude::*;
use std::sync::Arc;
use sysinfo::System;
/// Cache for component status to avoid spawning pgrep processes too frequently
struct ComponentStatusCache {
statuses: Vec<(String, bool, String)>, // (name, is_running, port)
last_check: std::time::Instant,
}
impl ComponentStatusCache {
fn new() -> Self {
Self {
statuses: Vec::new(),
last_check: std::time::Instant::now() - std::time::Duration::from_secs(60),
}
}
fn needs_refresh(&self) -> bool {
// Only check component status every 10 seconds
self.last_check.elapsed() > std::time::Duration::from_secs(10)
}
fn refresh(&mut self) {
let components = vec![
("Tables", "postgres", "5432"),
("Cache", "valkey-server", "6379"),
("Drive", "minio", "9000"),
("LLM", "llama-server", "8081"),
];
self.statuses.clear();
for (comp_name, process, port) in components {
let is_running = Self::check_component_running(process);
self.statuses
.push((comp_name.to_string(), is_running, port.to_string()));
}
self.last_check = std::time::Instant::now();
}
fn check_component_running(process_name: &str) -> bool {
SafeCommand::new("pgrep")
.and_then(|c| c.arg("-f"))
.and_then(|c| c.arg(process_name))
.ok()
.and_then(|cmd| cmd.execute().ok())
.map(|output| !output.stdout.is_empty())
.unwrap_or(false)
}
fn get_statuses(&self) -> &[(String, bool, String)] {
&self.statuses
}
}
/// Cache for bot list to avoid database queries too frequently
struct BotListCache {
bot_list: Vec<(String, uuid::Uuid)>,
last_check: std::time::Instant,
}
impl BotListCache {
fn new() -> Self {
Self {
bot_list: Vec::new(),
last_check: std::time::Instant::now() - std::time::Duration::from_secs(60),
}
}
fn needs_refresh(&self) -> bool {
// Only query database every 5 seconds
self.last_check.elapsed() > std::time::Duration::from_secs(5)
}
fn refresh(&mut self, app_state: &Arc<AppState>) {
if let Ok(mut conn) = app_state.conn.get() {
if let Ok(list) = bots
.filter(is_active.eq(true))
.select((name, id))
.load::<(String, uuid::Uuid)>(&mut *conn)
{
self.bot_list = list;
}
}
self.last_check = std::time::Instant::now();
}
fn get_bots(&self) -> &[(String, uuid::Uuid)] {
&self.bot_list
}
}
pub struct StatusPanel {
app_state: Arc<AppState>,
last_update: std::time::Instant,
last_system_refresh: std::time::Instant,
cached_content: String,
system: System,
component_cache: ComponentStatusCache,
bot_cache: BotListCache,
}
impl std::fmt::Debug for StatusPanel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StatusPanel")
.field("app_state", &"Arc<AppState>")
.field("last_update", &self.last_update)
.field("cached_content_len", &self.cached_content.len())
.field("system", &"System")
.finish()
}
}
impl StatusPanel {
pub fn new(app_state: Arc<AppState>) -> Self {
// Only initialize with CPU and memory info, not all system info
let mut system = System::new();
system.refresh_cpu_all();
system.refresh_memory();
Self {
app_state,
last_update: std::time::Instant::now(),
last_system_refresh: std::time::Instant::now(),
cached_content: String::new(),
system,
component_cache: ComponentStatusCache::new(),
bot_cache: BotListCache::new(),
}
}
pub fn update(&mut self) -> Result<(), std::io::Error> {
// Only refresh system metrics every 2 seconds instead of every call
// This is the main CPU hog - refresh_all() is very expensive
if self.last_system_refresh.elapsed() > std::time::Duration::from_secs(2) {
// Only refresh CPU and memory, not ALL system info
self.system.refresh_cpu_all();
self.system.refresh_memory();
self.last_system_refresh = std::time::Instant::now();
}
// Refresh component status cache if needed (every 10 seconds)
if self.component_cache.needs_refresh() {
self.component_cache.refresh();
}
// Refresh bot list cache if needed (every 5 seconds)
if self.bot_cache.needs_refresh() {
self.bot_cache.refresh(&self.app_state);
}
self.cached_content = self.render(None);
self.last_update = std::time::Instant::now();
Ok(())
}
pub fn render(&mut self, selected_bot: Option<String>) -> String {
let mut lines = vec![
"╔═══════════════════════════════════════╗".to_string(),
"║ SYSTEM METRICS ║".to_string(),
"╚═══════════════════════════════════════╝".to_string(),
String::new(),
];
// Use cached CPU usage - don't refresh here
let cpu_usage = self.system.global_cpu_usage();
let cpu_bar = Self::create_progress_bar(cpu_usage, 20);
lines.push(format!(" CPU: {:5.1}% {}", cpu_usage, cpu_bar));
#[cfg(feature = "nvidia")]
{
let system_metrics = get_system_metrics().unwrap_or_default();
if let Some(gpu_usage) = system_metrics.gpu_usage {
let gpu_bar = Self::create_progress_bar(gpu_usage, 20);
lines.push(format!(" GPU: {:5.1}% {}", gpu_usage, gpu_bar));
} else {
lines.push(" GPU: Not available".to_string());
}
}
#[cfg(not(feature = "nvidia"))]
{
lines.push(" GPU: Feature not enabled".to_string());
}
let total_mem = self.system.total_memory() as f32 / 1024.0 / 1024.0 / 1024.0;
let used_mem = self.system.used_memory() as f32 / 1024.0 / 1024.0 / 1024.0;
let mem_percentage = if total_mem > 0.0 {
(used_mem / total_mem) * 100.0
} else {
0.0
};
let mem_bar = Self::create_progress_bar(mem_percentage, 20);
lines.push(format!(
" MEM: {:5.1}% {} ({:.1}/{:.1} GB)",
mem_percentage, mem_bar, used_mem, total_mem
));
lines.push("".to_string());
lines.push("╔═══════════════════════════════════════╗".to_string());
lines.push("║ COMPONENTS STATUS ║".to_string());
lines.push("╚═══════════════════════════════════════╝".to_string());
lines.push("".to_string());
// Use cached component statuses instead of spawning pgrep every time
for (comp_name, is_running, port) in self.component_cache.get_statuses() {
let status = if *is_running {
format!(" ONLINE [Port: {}]", port)
} else {
" OFFLINE".to_string()
};
lines.push(format!(" {:<10} {}", comp_name, status));
}
lines.push("".to_string());
lines.push("╔═══════════════════════════════════════╗".to_string());
lines.push("║ ACTIVE BOTS ║".to_string());
lines.push("╚═══════════════════════════════════════╝".to_string());
lines.push("".to_string());
// Use cached bot list instead of querying database every time
let bot_list = self.bot_cache.get_bots();
if bot_list.is_empty() {
lines.push(" No active bots".to_string());
} else {
for (bot_name, bot_id) in bot_list {
let marker = if let Some(ref selected) = selected_bot {
if selected == bot_name {
""
} else {
" "
}
} else {
" "
};
lines.push(format!(" {} {}", marker, bot_name));
if let Some(ref selected) = selected_bot {
if selected == bot_name {
lines.push("".to_string());
lines.push(" ┌─ Bot Configuration ─────────┐".to_string());
let config_manager = ConfigManager::new(self.app_state.conn.clone());
let llm_model = config_manager
.get_config(bot_id, "llm-model", None)
.unwrap_or_else(|_| "N/A".to_string());
lines.push(format!(" Model: {}", llm_model));
let ctx_size = config_manager
.get_config(bot_id, "llm-server-ctx-size", None)
.unwrap_or_else(|_| "N/A".to_string());
lines.push(format!(" Context: {}", ctx_size));
let temp = config_manager
.get_config(bot_id, "llm-temperature", None)
.unwrap_or_else(|_| "N/A".to_string());
lines.push(format!(" Temp: {}", temp));
lines.push(" └─────────────────────────────┘".to_string());
}
}
}
}
lines.push("".to_string());
lines.push("╔═══════════════════════════════════════╗".to_string());
lines.push("║ SESSIONS ║".to_string());
lines.push("╚═══════════════════════════════════════╝".to_string());
let session_count = self
.app_state
.response_channels
.try_lock()
.map(|channels| channels.len())
.unwrap_or(0);
lines.push(format!(" Active Sessions: {}", session_count));
lines.join("\n")
}
fn create_progress_bar(percentage: f32, width: usize) -> String {
let filled = (percentage / 100.0 * width as f32).round() as usize;
let empty = width.saturating_sub(filled);
let filled_chars = "".repeat(filled);
let empty_chars = "".repeat(empty);
format!("[{}{}]", filled_chars, empty_chars)
}
pub fn check_component_running(process_name: &str) -> bool {
ComponentStatusCache::check_component_running(process_name)
}
}