From e679896ca049e8e7b97350aad1d6ac90ae26f69d Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Tue, 9 Sep 2025 15:51:44 -0700 Subject: [PATCH] Add AI generated zliij plugin scafold --- zellij_socktop_plugin/Cargo.toml | 18 +++ zellij_socktop_plugin/README.md | 101 ++++++++++++++++ zellij_socktop_plugin/plugin.yaml | 11 ++ zellij_socktop_plugin/src/lib.rs | 186 ++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 zellij_socktop_plugin/Cargo.toml create mode 100644 zellij_socktop_plugin/README.md create mode 100644 zellij_socktop_plugin/plugin.yaml create mode 100644 zellij_socktop_plugin/src/lib.rs diff --git a/zellij_socktop_plugin/Cargo.toml b/zellij_socktop_plugin/Cargo.toml new file mode 100644 index 0000000..ba82766 --- /dev/null +++ b/zellij_socktop_plugin/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "zellij_socktop_plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +zellij-tile = "0.40.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +socktop_connector = { version = "0.1.5", default-features = false, features = ["wasm"] } + +[dependencies.chrono] +version = "0.4" +default-features = false +features = ["clock", "std", "wasmbind"] diff --git a/zellij_socktop_plugin/README.md b/zellij_socktop_plugin/README.md new file mode 100644 index 0000000..1c26879 --- /dev/null +++ b/zellij_socktop_plugin/README.md @@ -0,0 +1,101 @@ +# Zellij Socktop Plugin + +A Zellij plugin that displays real-time system metrics from a socktop agent. + +## Quick Start + +1. **Build the plugin:** + ```bash + cargo build --target wasm32-wasi --release + ``` + +2. **Install in Zellij:** + ```bash + # Copy the WASM file to Zellij plugins directory + mkdir -p ~/.config/zellij/plugins + cp target/wasm32-wasi/release/zellij_socktop_plugin.wasm ~/.config/zellij/plugins/socktop.wasm + cp plugin.yaml ~/.config/zellij/plugins/ + ``` + +3. **Use in Zellij layout:** + ```yaml + # ~/.config/zellij/layouts/socktop.yaml + template: + direction: Horizontal + parts: + - direction: Vertical + borderless: true + split_size: + Fixed: 1 + run: + plugin: + location: "file:~/.config/zellij/plugins/socktop.wasm" + configuration: + server_url: "ws://localhost:3000/ws" + - direction: Vertical + ``` + +4. **Launch Zellij with the layout:** + ```bash + zellij --layout socktop + ``` + +## Plugin Features + +- **Real-time Metrics**: Displays CPU and memory usage +- **Auto-refresh**: Updates every 2 seconds +- **Reconnection**: Press 'r' to reconnect to socktop agent +- **Configurable**: Set custom server URL in plugin config +- **Error Handling**: Shows connection status and errors + +## Configuration Options + +- `server_url`: WebSocket URL for socktop agent (default: `ws://localhost:3000/ws`) + +## Controls + +- **`r`** - Reconnect to socktop agent +- Plugin updates automatically every 2 seconds + +## Development Notes + +This is a scaffold implementation. To make it fully functional: + +1. **Async Operations**: Zellij plugins have limitations with async operations. You may need to: + - Use a different async runtime or approach + - Handle WebSocket connections in a background thread + - Use message passing between threads + +2. **Error Handling**: Add more robust error handling for: + - Network connectivity issues + - Invalid server URLs + - Agent unavailability + +3. **UI Improvements**: + - Add more detailed metrics display + - Implement scrolling for large datasets + - Add color coding for status indicators + +4. **Performance**: + - Implement caching to reduce agent requests + - Add configurable update intervals + - Optimize WASM binary size + +## Dependencies + +- `zellij-tile`: Zellij plugin framework +- `socktop_connector`: WebSocket connector with WASM support +- `serde`: JSON serialization +- `chrono`: Time handling (WASM-compatible) + +## Building + +```bash +# Add WASM target +rustup target add wasm32-wasi + +# Build for WASM +cargo build --target wasm32-wasi --release + +# The plugin will be at: target/wasm32-wasi/release/zellij_socktop_plugin.wasm +``` diff --git a/zellij_socktop_plugin/plugin.yaml b/zellij_socktop_plugin/plugin.yaml new file mode 100644 index 0000000..840a3e7 --- /dev/null +++ b/zellij_socktop_plugin/plugin.yaml @@ -0,0 +1,11 @@ +name: "socktop" +version: "0.1.0" +authors: ["Your Name "] +plugin: true +permissions: + - ReadApplicationState +configuration: + server_url: + type: "string" + default: "ws://localhost:3000/ws" + description: "WebSocket URL for socktop agent" diff --git a/zellij_socktop_plugin/src/lib.rs b/zellij_socktop_plugin/src/lib.rs new file mode 100644 index 0000000..e0b3913 --- /dev/null +++ b/zellij_socktop_plugin/src/lib.rs @@ -0,0 +1,186 @@ +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, + metrics_data: Option, + connection_status: String, + error_message: Option, + 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) { + // 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 { + // In a real implementation, you would: + // 1. Make an async call to connector.request(AgentRequest::Metrics) + // 2. Handle the response and update STATE.metrics_data + // 3. Handle errors and update STATE.error_message + + // For this scaffold, we'll simulate a response + match STATE.update_counter % 4 { + 0 => { + STATE.metrics_data = Some("CPU: 45.2%, Memory: 67.8%".to_string()); + STATE.connection_status = "Active".to_string(); + } + 1 => { + STATE.metrics_data = Some("CPU: 32.1%, Memory: 71.3%".to_string()); + } + 2 => { + STATE.metrics_data = Some("CPU: 58.7%, Memory: 69.1%".to_string()); + } + _ => { + STATE.metrics_data = Some("CPU: 41.9%, Memory: 72.4%".to_string()); + } + } + } else { + STATE.error_message = Some("No connector available".to_string()); + STATE.connection_status = "Disconnected".to_string(); + } + } + } +} + +// Async helper for real WebSocket operations (commented out for scaffold) +/* +async fn connect_and_fetch(connector: &mut SocktopConnector) -> Result { + // Connect to socktop agent + connector.connect().await + .map_err(|e| format!("Connection failed: {}", e))?; + + // Request metrics + let response = connector.request(AgentRequest::Metrics).await + .map_err(|e| format!("Metrics request failed: {}", e))?; + + // Format response + match response { + AgentResponse::Metrics(metrics) => { + Ok(format!("CPU: {:.1}%, Mem: {:.1}%, Host: {}", + metrics.cpu_total, + (metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0, + metrics.hostname + )) + } + _ => Err("Unexpected response type".to_string()) + } +} +*/