Add mdBook documentation with Ghostty-style sidebar
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# WebSocket API Integration
|
||||
|
||||
Integrate with the socktop agent's WebSocket API to build custom monitoring tools.
|
||||
|
||||
## WebSocket Endpoint
|
||||
|
||||
```
|
||||
ws://HOST:PORT/ws # Without TLS
|
||||
wss://HOST:PORT/ws # With TLS
|
||||
```
|
||||
|
||||
With authentication token (if configured):
|
||||
```
|
||||
ws://HOST:PORT/ws?token=YOUR_TOKEN
|
||||
wss://HOST:PORT/ws?token=YOUR_TOKEN
|
||||
```
|
||||
|
||||
## Request Types
|
||||
|
||||
Send JSON messages to request specific metrics:
|
||||
|
||||
```json
|
||||
{"type": "metrics"} // Fast-changing metrics (CPU, memory, network)
|
||||
{"type": "disks"} // Disk information
|
||||
{"type": "processes"} // Process list (returns protobuf)
|
||||
```
|
||||
|
||||
## Response Formats
|
||||
|
||||
### Metrics (JSON)
|
||||
|
||||
```json
|
||||
{
|
||||
"cpu_total": 12.4,
|
||||
"cpu_per_core": [11.2, 15.7],
|
||||
"mem_total": 33554432,
|
||||
"mem_used": 18321408,
|
||||
"swap_total": 0,
|
||||
"swap_used": 0,
|
||||
"hostname": "myserver",
|
||||
"cpu_temp_c": 42.5,
|
||||
"networks": [{"name":"eth0","received":12345678,"transmitted":87654321}],
|
||||
"gpus": [{"name":"nvidia-0","usage":56.7,"memory_total":8589934592,"memory_used":1073741824,"temp_c":65.0}]
|
||||
}
|
||||
```
|
||||
|
||||
### Disks (JSON)
|
||||
|
||||
```json
|
||||
[
|
||||
{"name":"nvme0n1p2","total":512000000000,"available":320000000000},
|
||||
{"name":"sda1","total":1000000000000,"available":750000000000}
|
||||
]
|
||||
```
|
||||
|
||||
### Processes (Protocol Buffers)
|
||||
|
||||
Processes are returned in protobuf format, optionally gzip-compressed. Schema:
|
||||
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
message Process {
|
||||
uint32 pid = 1;
|
||||
string name = 2;
|
||||
float cpu_usage = 3;
|
||||
uint64 mem_bytes = 4;
|
||||
}
|
||||
|
||||
message ProcessList {
|
||||
uint32 process_count = 1;
|
||||
repeated Process processes = 2;
|
||||
}
|
||||
```
|
||||
|
||||
## Example: JavaScript/Node.js
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://localhost:3000/ws');
|
||||
|
||||
ws.on('open', function open() {
|
||||
console.log('Connected to socktop_agent');
|
||||
|
||||
// Request metrics
|
||||
ws.send(JSON.stringify({type: 'metrics'}));
|
||||
|
||||
// Poll every second
|
||||
setInterval(() => {
|
||||
ws.send(JSON.stringify({type: 'metrics'}));
|
||||
}, 1000);
|
||||
|
||||
// Request processes every 3 seconds
|
||||
setInterval(() => {
|
||||
ws.send(JSON.stringify({type: 'processes'}));
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
ws.on('message', function incoming(data) {
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
console.log('Received JSON data:', jsonData);
|
||||
} catch (e) {
|
||||
console.log('Received binary data (protobuf), length:', data.length);
|
||||
// Process binary protobuf data with protobufjs
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', function close() {
|
||||
console.log('Disconnected from socktop_agent');
|
||||
});
|
||||
```
|
||||
|
||||
## Example: Python
|
||||
|
||||
```python
|
||||
import json
|
||||
import asyncio
|
||||
import websockets
|
||||
|
||||
async def monitor_system():
|
||||
uri = "ws://localhost:3000/ws"
|
||||
async with websockets.connect(uri) as websocket:
|
||||
print("Connected to socktop_agent")
|
||||
|
||||
# Request initial metrics
|
||||
await websocket.send(json.dumps({"type": "metrics"}))
|
||||
|
||||
while True:
|
||||
# Request metrics
|
||||
await websocket.send(json.dumps({"type": "metrics"}))
|
||||
|
||||
# Receive response
|
||||
response = await websocket.recv()
|
||||
|
||||
try:
|
||||
data = json.loads(response)
|
||||
print(f"CPU: {data['cpu_total']}%, Memory: {data['mem_used']/data['mem_total']*100:.1f}%")
|
||||
except json.JSONDecodeError:
|
||||
print(f"Received binary data, length: {len(response)}")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
asyncio.run(monitor_system())
|
||||
```
|
||||
|
||||
## Recommended Intervals
|
||||
|
||||
- Metrics: ≥ 500ms
|
||||
- Processes: ≥ 2000ms
|
||||
- Disks: ≥ 5000ms
|
||||
|
||||
## Handling Protocol Buffers
|
||||
|
||||
For processing binary process data:
|
||||
|
||||
1. Check if response starts with gzip magic bytes (`0x1f, 0x8b`)
|
||||
2. Decompress if necessary
|
||||
3. Parse with protobuf library using the schema above
|
||||
|
||||
## Error Handling
|
||||
|
||||
Implement reconnection logic with exponential backoff:
|
||||
|
||||
```javascript
|
||||
function connect() {
|
||||
const ws = new WebSocket('ws://localhost:3000/ws');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('Connected');
|
||||
// Start polling
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('Connection lost, reconnecting...');
|
||||
setTimeout(connect, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
connect();
|
||||
```
|
||||
|
||||
## More Info
|
||||
|
||||
For detailed implementation, see the [socktop_agent README](https://github.com/jasonwitty/socktop/tree/master/socktop_agent).
|
||||
@@ -0,0 +1,437 @@
|
||||
# Socktop Connector Library
|
||||
|
||||
The `socktop_connector` library provides a high-level interface for connecting to socktop agents programmatically.
|
||||
|
||||
## Overview
|
||||
|
||||
The connector library allows you to:
|
||||
|
||||
- **Build custom monitoring tools** - Create your own dashboards and UIs
|
||||
- **Integrate with existing systems** - Add socktop metrics to your applications
|
||||
- **Automate monitoring** - Script-based system checks and alerts
|
||||
- **WASM support** - Use in browser-based applications
|
||||
|
||||
## Installation
|
||||
|
||||
Add to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
socktop_connector = "1.50"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Connection
|
||||
|
||||
```rust
|
||||
use socktop_connector::{connect_to_socktop_agent, AgentRequest, AgentResponse};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Connect to agent
|
||||
let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?;
|
||||
|
||||
// Request metrics
|
||||
if let Ok(AgentResponse::Metrics(metrics)) = connector.request(AgentRequest::Metrics).await {
|
||||
println!("Hostname: {}", metrics.hostname);
|
||||
println!("CPU Usage: {:.1}%", metrics.cpu_total);
|
||||
println!("Memory: {:.1} GB / {:.1} GB",
|
||||
metrics.mem_used as f64 / 1_000_000_000.0,
|
||||
metrics.mem_total as f64 / 1_000_000_000.0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### With TLS
|
||||
|
||||
```rust
|
||||
use socktop_connector::connect_to_socktop_agent_with_tls;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let connector = connect_to_socktop_agent_with_tls(
|
||||
"wss://secure-host:3000/ws",
|
||||
"/path/to/ca.pem",
|
||||
false // Enable hostname verification
|
||||
).await?;
|
||||
|
||||
// Use connector...
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Request Types
|
||||
|
||||
The connector supports several request types:
|
||||
|
||||
### Metrics Request
|
||||
|
||||
Get comprehensive system metrics:
|
||||
|
||||
```rust
|
||||
use socktop_connector::{AgentRequest, AgentResponse};
|
||||
|
||||
match connector.request(AgentRequest::Metrics).await {
|
||||
Ok(AgentResponse::Metrics(metrics)) => {
|
||||
println!("CPU Total: {:.1}%", metrics.cpu_total);
|
||||
|
||||
// Per-core usage
|
||||
for (i, usage) in metrics.cpu_per_core.iter().enumerate() {
|
||||
println!("Core {}: {:.1}%", i, usage);
|
||||
}
|
||||
|
||||
// CPU temperature
|
||||
if let Some(temp) = metrics.cpu_temp_c {
|
||||
println!("CPU Temperature: {:.1}°C", temp);
|
||||
}
|
||||
|
||||
// Memory
|
||||
println!("Memory Used: {} bytes", metrics.mem_used);
|
||||
println!("Memory Total: {} bytes", metrics.mem_total);
|
||||
|
||||
// Swap
|
||||
println!("Swap Used: {} bytes", metrics.swap_used);
|
||||
println!("Swap Total: {} bytes", metrics.swap_total);
|
||||
|
||||
// Network interfaces
|
||||
for net in &metrics.networks {
|
||||
println!("Interface {}: ↓{} ↑{}",
|
||||
net.name, net.received, net.transmitted);
|
||||
}
|
||||
|
||||
// GPU information
|
||||
if let Some(gpus) = &metrics.gpus {
|
||||
for gpu in gpus {
|
||||
if let Some(name) = &gpu.name {
|
||||
println!("GPU: {}", name);
|
||||
println!(" Utilization: {:.1}%", gpu.utilization.unwrap_or(0.0));
|
||||
if let Some(temp) = gpu.temp {
|
||||
println!(" Temperature: {:.1}°C", temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error: {}", e),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
```
|
||||
|
||||
### Process Request
|
||||
|
||||
Get process information:
|
||||
|
||||
```rust
|
||||
match connector.request(AgentRequest::Processes).await {
|
||||
Ok(AgentResponse::Processes(processes)) => {
|
||||
println!("Total processes: {}", processes.process_count);
|
||||
|
||||
for proc in &processes.top_processes {
|
||||
println!("PID {}: {} - CPU: {:.1}%, Mem: {} MB",
|
||||
proc.pid,
|
||||
proc.name,
|
||||
proc.cpu_usage,
|
||||
proc.mem_bytes / 1_000_000);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error: {}", e),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
```
|
||||
|
||||
### Disk Request
|
||||
|
||||
Get disk information:
|
||||
|
||||
```rust
|
||||
match connector.request(AgentRequest::Disks).await {
|
||||
Ok(AgentResponse::Disks(disks)) => {
|
||||
for disk in disks {
|
||||
let used = disk.total - disk.available;
|
||||
let used_gb = used as f64 / 1_000_000_000.0;
|
||||
let total_gb = disk.total as f64 / 1_000_000_000.0;
|
||||
let percent = (used as f64 / disk.total as f64) * 100.0;
|
||||
|
||||
println!("Disk {}: {:.1} GB / {:.1} GB ({:.1}%)",
|
||||
disk.name, used_gb, total_gb, percent);
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error: {}", e),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
```
|
||||
|
||||
## Continuous Monitoring
|
||||
|
||||
Monitor metrics in a loop:
|
||||
|
||||
```rust
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?;
|
||||
|
||||
loop {
|
||||
match connector.request(AgentRequest::Metrics).await {
|
||||
Ok(AgentResponse::Metrics(metrics)) => {
|
||||
println!("CPU: {:.1}%, Memory: {:.1}%",
|
||||
metrics.cpu_total,
|
||||
(metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Connection error: {}", e);
|
||||
break;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Configuration
|
||||
|
||||
```rust
|
||||
use socktop_connector::{ConnectorConfig, SocktopConnector};
|
||||
|
||||
let config = ConnectorConfig {
|
||||
url: "ws://server:3000/ws".to_string(),
|
||||
token: Some("secret-token".to_string()),
|
||||
ca_cert_path: Some("/path/to/ca.pem".to_string()),
|
||||
verify_tls: true,
|
||||
};
|
||||
|
||||
let connector = SocktopConnector::connect_with_config(config).await?;
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```rust
|
||||
use socktop_connector::{ConnectorError, Result};
|
||||
|
||||
async fn monitor() -> Result<()> {
|
||||
let mut connector = connect_to_socktop_agent("ws://server:3000/ws").await?;
|
||||
|
||||
match connector.request(AgentRequest::Metrics).await {
|
||||
Ok(response) => {
|
||||
// Handle response
|
||||
Ok(())
|
||||
}
|
||||
Err(ConnectorError::ConnectionClosed) => {
|
||||
eprintln!("Connection closed, attempting reconnect...");
|
||||
Err(ConnectorError::ConnectionClosed)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## WASM Support
|
||||
|
||||
The connector supports WebAssembly for browser usage:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
socktop_connector = { version = "1.50", features = ["wasm"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
use socktop_connector::connect_to_socktop_agent;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn monitor_system(url: String) -> Result<JsValue, JsValue> {
|
||||
let mut connector = connect_to_socktop_agent(&url)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&e.to_string()))?;
|
||||
|
||||
match connector.request(AgentRequest::Metrics).await {
|
||||
Ok(AgentResponse::Metrics(metrics)) => {
|
||||
Ok(JsValue::from_str(&format!("CPU: {:.1}%", metrics.cpu_total)))
|
||||
}
|
||||
Err(e) => Err(JsValue::from_str(&e.to_string())),
|
||||
_ => Err(JsValue::from_str("Unexpected response")),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Building Custom Applications
|
||||
|
||||
### Example: Simple Dashboard
|
||||
|
||||
```rust
|
||||
use socktop_connector::*;
|
||||
use tokio::time::{interval, Duration};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let servers = vec![
|
||||
("web", "ws://web.example.com:3000/ws"),
|
||||
("db", "ws://db.example.com:3000/ws"),
|
||||
("cache", "ws://cache.example.com:3000/ws"),
|
||||
];
|
||||
|
||||
let mut connectors = Vec::new();
|
||||
for (name, url) in servers {
|
||||
match connect_to_socktop_agent(url).await {
|
||||
Ok(conn) => connectors.push((name, conn)),
|
||||
Err(e) => eprintln!("Failed to connect to {}: {}", name, e),
|
||||
}
|
||||
}
|
||||
|
||||
let mut tick = interval(Duration::from_secs(2));
|
||||
|
||||
loop {
|
||||
tick.tick().await;
|
||||
|
||||
for (name, connector) in &mut connectors {
|
||||
if let Ok(AgentResponse::Metrics(m)) = connector.request(AgentRequest::Metrics).await {
|
||||
println!("[{}] CPU: {:.1}%, Mem: {:.1}%",
|
||||
name,
|
||||
m.cpu_total,
|
||||
(m.mem_used as f64 / m.mem_total as f64) * 100.0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("---");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Alert System
|
||||
|
||||
```rust
|
||||
use socktop_connector::*;
|
||||
|
||||
async fn check_alerts(mut connector: SocktopConnector) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match connector.request(AgentRequest::Metrics).await {
|
||||
Ok(AgentResponse::Metrics(metrics)) => {
|
||||
// CPU alert
|
||||
if metrics.cpu_total > 90.0 {
|
||||
eprintln!("ALERT: CPU usage at {:.1}%", metrics.cpu_total);
|
||||
}
|
||||
|
||||
// Memory alert
|
||||
let mem_percent = (metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0;
|
||||
if mem_percent > 90.0 {
|
||||
eprintln!("ALERT: Memory usage at {:.1}%", mem_percent);
|
||||
}
|
||||
|
||||
// Disk alert
|
||||
if let Ok(AgentResponse::Disks(disks)) = connector.request(AgentRequest::Disks).await {
|
||||
for disk in disks {
|
||||
let used_percent = ((disk.total - disk.available) as f64 / disk.total as f64) * 100.0;
|
||||
if used_percent > 90.0 {
|
||||
eprintln!("ALERT: Disk {} at {:.1}%", disk.name, used_percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Error fetching metrics: {}", e),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Data Types
|
||||
|
||||
Key types provided by the library:
|
||||
|
||||
- `Metrics` - System metrics (CPU, memory, network, GPU, etc.)
|
||||
- `ProcessMetricsResponse` - Process information
|
||||
- `DiskInfo` - Disk usage information
|
||||
- `NetworkInfo` - Network interface statistics
|
||||
- `GpuInfo` - GPU metrics
|
||||
- `JournalEntry` - Systemd journal entries
|
||||
- `AgentRequest` - Request types
|
||||
- `AgentResponse` - Response types
|
||||
|
||||
See the [crate documentation](https://docs.rs/socktop_connector) for complete API reference.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
The connector is lightweight and efficient:
|
||||
|
||||
- **Protocol Buffers** - Efficient binary serialization
|
||||
- **Gzip compression** - Reduced bandwidth usage
|
||||
- **Async I/O** - Non-blocking operations
|
||||
- **Connection reuse** - Single WebSocket for multiple requests
|
||||
|
||||
Typical resource usage:
|
||||
- **Memory**: ~1-5 MB per connection
|
||||
- **CPU**: < 0.1% during idle
|
||||
- **Bandwidth**: ~1-5 KB per metrics request
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Errors
|
||||
|
||||
```rust
|
||||
match connect_to_socktop_agent(url).await {
|
||||
Err(ConnectorError::ConnectionFailed(e)) => {
|
||||
eprintln!("Connection failed: {}", e);
|
||||
// Retry logic here
|
||||
}
|
||||
Err(ConnectorError::InvalidUrl) => {
|
||||
eprintln!("Invalid URL format");
|
||||
}
|
||||
Err(e) => eprintln!("Other error: {}", e),
|
||||
Ok(conn) => { /* Success */ }
|
||||
}
|
||||
```
|
||||
|
||||
### TLS Errors
|
||||
|
||||
```rust
|
||||
// Disable TLS verification for testing (not recommended)
|
||||
use socktop_connector::{ConnectorConfig, SocktopConnector};
|
||||
|
||||
let config = ConnectorConfig {
|
||||
url: "wss://server:3000/ws".to_string(),
|
||||
verify_tls: false,
|
||||
..Default::default()
|
||||
};
|
||||
```
|
||||
|
||||
## Examples Repository
|
||||
|
||||
More examples available in the socktop repository:
|
||||
|
||||
- `examples/simple_monitor.rs` - Basic monitoring
|
||||
- `examples/multi_server.rs` - Monitor multiple servers
|
||||
- `examples/alert_system.rs` - Threshold-based alerts
|
||||
- `examples/wasm_demo/` - Browser-based monitoring
|
||||
|
||||
## API Reference
|
||||
|
||||
Full API documentation: [docs.rs/socktop_connector](https://docs.rs/socktop_connector)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Agent Direct Integration](./agent-integration.md) - Embed agent in your app
|
||||
- [General Usage](../usage/general.md) - Using the TUI client
|
||||
- [Configuration](../usage/configuration.md) - Configuration options
|
||||
|
||||
<!-- TODO: Add more documentation -->
|
||||
<!-- TODO: Add WebSocket reconnection examples -->
|
||||
<!-- TODO: Add rate limiting guidance -->
|
||||
<!-- TODO: Add batch request examples -->
|
||||
<!-- TODO: Add Prometheus exporter example -->
|
||||
@@ -0,0 +1,57 @@
|
||||
# Monitor Multiple Hosts with tmux
|
||||
|
||||
Use tmux to show multiple socktop instances in a single terminal.
|
||||
|
||||

|
||||
*monitoring 4 Raspberry Pis using Tmux*
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Install tmux:
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install tmux
|
||||
```
|
||||
|
||||
## Two panes (left/right)
|
||||
|
||||
This creates a session named "socktop", splits it horizontally, and starts two socktops.
|
||||
|
||||
```bash
|
||||
tmux new-session -d -s socktop 'socktop ws://HOST1:3000/ws' \; \
|
||||
split-window -h 'socktop ws://HOST2:3000/ws' \; \
|
||||
select-layout even-horizontal \; \
|
||||
attach
|
||||
```
|
||||
|
||||
## Four panes (2x2 grid)
|
||||
|
||||
This creates a 2x2 grid with one socktop per pane.
|
||||
|
||||
```bash
|
||||
tmux new-session -d -s socktop 'socktop ws://HOST1:3000/ws' \; \
|
||||
split-window -h 'socktop ws://HOST2:3000/ws' \; \
|
||||
select-pane -t 0 \; split-window -v 'socktop ws://HOST3:3000/ws' \; \
|
||||
select-pane -t 1 \; split-window -v 'socktop ws://HOST4:3000/ws' \; \
|
||||
select-layout tiled \; \
|
||||
attach
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- Replace HOST1..HOST4 (and ports) with your targets
|
||||
- Reattach later: `tmux attach -t socktop`
|
||||
|
||||
## Key bindings (defaults)
|
||||
|
||||
- Split left/right: `Ctrl-b %`
|
||||
- Split top/bottom: `Ctrl-b "`
|
||||
- Move between panes: `Ctrl-b` + Arrow keys
|
||||
- Show pane numbers: `Ctrl-b q`
|
||||
- Close a pane: `Ctrl-b x`
|
||||
- Detach from session: `Ctrl-b d`
|
||||
|
||||
## More Info
|
||||
|
||||
For detailed tmux documentation, see the [tmux GitHub](https://github.com/tmux/tmux).
|
||||
@@ -0,0 +1,72 @@
|
||||
# Monitor Multiple Hosts with Zellij
|
||||
|
||||
Use Zellij to monitor multiple socktop instances in a single terminal.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cargo install zellij
|
||||
```
|
||||
|
||||
## Example Layout
|
||||
|
||||
Create `socktop-layout.kdl`:
|
||||
|
||||
```kdl
|
||||
layout {
|
||||
pane split_direction="vertical" {
|
||||
pane command="socktop" {
|
||||
args "-P" "rpi-master"
|
||||
}
|
||||
pane command="socktop" {
|
||||
args "-P" "rpi-worker-1"
|
||||
}
|
||||
}
|
||||
pane split_direction="vertical" {
|
||||
pane command="socktop" {
|
||||
args "-P" "rpi-worker-2"
|
||||
}
|
||||
pane command="socktop" {
|
||||
args "-P" "rpi-worker-3"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
zellij --layout socktop-layout.kdl
|
||||
```
|
||||
|
||||
## More Info
|
||||
|
||||
For detailed Zellij documentation, see [Zellij](https://zellij.dev/).
|
||||
|
||||
Create `~/.config/zellij/layouts/socktop-monitoring.kdl`:
|
||||
|
||||
```kdl
|
||||
layout {
|
||||
pane_template name="socktop_pane" {
|
||||
command "socktop"
|
||||
args "-P" "{profile}"
|
||||
}
|
||||
|
||||
pane split_direction="vertical" {
|
||||
pane split_direction="horizontal" {
|
||||
socktop_pane profile="production-web"
|
||||
socktop_pane profile="production-db"
|
||||
}
|
||||
pane split_direction="horizontal" {
|
||||
socktop_pane profile="staging-web"
|
||||
socktop_pane profile="staging-db"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use the Layout
|
||||
|
||||
```bash
|
||||
zellij --layout socktop-monitoring
|
||||
```
|
||||
Reference in New Issue
Block a user