socktop/zellij_socktop_plugin/src/lib.rs
2025-09-10 09:32:00 -07:00

181 lines
6.4 KiB
Rust

use zellij_tile::prelude::*;
use serde::{Deserialize, Serialize};
use socktop_connector::{ConnectorConfig, AgentRequest, SocktopConnector, AgentResponse};
use std::collections::HashMap;
#[derive(Default)]
struct State {
connector: Option<SocktopConnector>,
metrics_data: Option<String>,
connection_status: String,
error_message: Option<String>,
update_counter: u32,
}
static mut STATE: State = State {
connector: None,
metrics_data: None,
connection_status: String::new(),
error_message: None,
update_counter: 0,
};
register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self, configuration: BTreeMap<String, String>) {
// Get server URL from plugin config or use default
let server_url = configuration
.get("server_url")
.cloned()
.unwrap_or_else(|| "ws://localhost:3000/ws".to_string());
// Initialize connector configuration
let config = ConnectorConfig::new(&server_url);
let connector = SocktopConnector::new(config);
unsafe {
STATE.connector = Some(connector);
STATE.connection_status = "Connecting...".to_string();
}
// Set up periodic updates
set_timeout(1.0); // Update every second
// Start initial connection
self.connect_to_socktop();
request_permission(&[
PermissionType::ReadApplicationState,
]);
}
fn update(&mut self, event: Event) -> bool {
match event {
Event::Timer(_) => {
unsafe {
STATE.update_counter += 1;
}
// Request metrics every update cycle
self.fetch_metrics();
// Set next timer
set_timeout(2.0); // Update every 2 seconds
true
}
Event::Key(key) => {
match key {
Key::Char('r') => {
// Reconnect on 'r' key press
self.connect_to_socktop();
true
}
_ => false,
}
}
_ => false,
}
}
fn render(&mut self, rows: usize, cols: usize) {
unsafe {
let mut output = Vec::new();
// Header
output.push("╭─ Socktop Metrics Plugin ─╮".to_string());
output.push(format!("│ Status: {:<18}", STATE.connection_status));
output.push("├──────────────────────────╯".to_string());
// Metrics display
if let Some(ref metrics) = STATE.metrics_data {
output.push("│ System Metrics:".to_string());
output.push(format!("{}", metrics));
} else if let Some(ref error) = STATE.error_message {
output.push("│ Error:".to_string());
output.push(format!("{}", error));
} else {
output.push("│ Waiting for data...".to_string());
}
// Footer
output.push("".to_string());
output.push(format!("│ Updates: {} │ Press 'r' to reconnect", STATE.update_counter));
output.push("╰──────────────────────────╯".to_string());
// Print lines within terminal bounds
for (i, line) in output.iter().enumerate() {
if i < rows {
println!("{}", line);
}
}
}
}
}
impl State {
fn connect_to_socktop(&mut self) {
unsafe {
if let Some(ref mut connector) = STATE.connector {
STATE.connection_status = "Connecting...".to_string();
STATE.error_message = None;
// In a real implementation, you'd use async/await here
// For this scaffold, we'll simulate the connection
// Note: Zellij plugins have limitations with async operations
STATE.connection_status = "Connected".to_string();
}
}
}
fn fetch_metrics(&mut self) {
unsafe {
if let Some(ref mut connector) = STATE.connector {
// Try to get real metrics from socktop agent
match self.try_get_metrics(connector) {
Ok(metrics_text) => {
STATE.metrics_data = Some(metrics_text);
STATE.connection_status = "Active".to_string();
STATE.error_message = None;
}
Err(error) => {
STATE.error_message = Some(error);
STATE.connection_status = "Error".to_string();
}
}
} else {
STATE.error_message = Some("No connector available".to_string());
STATE.connection_status = "Disconnected".to_string();
}
}
}
fn try_get_metrics(&mut self, connector: &mut SocktopConnector) -> Result<String, String> {
// Note: This is synchronous for simplicity. In a real plugin you might need
// to handle async operations differently depending on Zellij's threading model.
// For now, we'll use a blocking approach or return a placeholder
// that indicates we're trying to connect
// Attempt connection if not connected
if let Err(e) = futures::executor::block_on(connector.connect()) {
return Err(format!("Connection failed: {}", e));
}
// Request metrics
match futures::executor::block_on(connector.request(AgentRequest::Metrics)) {
Ok(AgentResponse::Metrics(metrics)) => {
Ok(format!(
"CPU: {:.1}% | Mem: {:.1}% | Host: {} | Load: {:.2}",
metrics.cpu_total,
(metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0,
metrics.hostname,
metrics.load_avg_1m
))
}
Ok(_) => Err("Unexpected response type".to_string()),
Err(e) => Err(format!("Request failed: {}", e)),
}
}
}