Widgets are executable scripts that output structured text to stdout. ServerHub parses this output to render dashboard widgets.
Sets the widget title displayed in the header.
title: CPU Usage
Suggested refresh interval in seconds. Note: The config file refresh value takes precedence over this.
refresh: 5
A display row. Supports inline elements and Spectre.Console markup.
row: Hello World
row: [status:ok] All systems operational
row: [progress:75:inline]
row: [grey70]Muted text[/]
Defines an interactive action that users can execute from the expanded widget view.
Basic Syntax:
action: Label:command
action: [flags] Label:command
Flags (comma-separated in brackets):
| Flag | Description |
|---|---|
danger |
Shows warning indicator, requires confirmation |
sudo |
Executes with elevated privileges (prompts for password if needed) |
refresh |
Refreshes the widget after successful execution |
timeout=N |
Custom timeout in seconds (default: 60) |
Timeout Values:
timeout=N- Command times out after N secondstimeout=0- No timeout (runs indefinitely until terminated)- No flag - Uses default 60 second timeout
Examples:
# Simple action
action: View logs:journalctl -n 50
# Action with flags
action: [danger,refresh] Restart nginx:systemctl restart nginx
action: [sudo] Update packages:apt update
action: [sudo,danger,refresh] Reboot:reboot
# Actions with custom timeout
action: [sudo,timeout=120] Update cache:apt update
action: [sudo,danger,timeout=600] Full upgrade:apt full-upgrade -y
action: [timeout=0] View live log:journalctl -f -n 100UI Behavior:
- Actions appear as buttons in the expanded widget view
dangeractions show a warning banner before executionsudoactions show a password prompt if credentials aren't cachedtimeout=0actions show a "no timeout limit" warning- Progress bar shows elapsed time vs timeout (or pulsing animation for infinite)
Displays a colored status icon before the row content.
[status:STATE]
| State | Color | Use Case |
|---|---|---|
ok |
Green | Normal operation |
info |
Blue | Informational |
warn |
Yellow | Warning condition |
error |
Red | Error/critical state |
Example:
row: [status:ok] Service running
row: [status:warn] High memory usage
row: [status:error] Disk full
Displays a visual progress bar with optional gradient coloring.
[progress:VALUE]
[progress:VALUE:GRADIENT]
[progress:VALUE:GRADIENT:STYLE]
[progress:VALUE:STYLE]
- VALUE: Integer 0-100
- GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- STYLE:
inline(default) orchart
Default Behavior (no gradient specified):
- Green for 0-69%
- Yellow for 70-89%
- Red for 90-100%
Predefined Gradients:
cool- Blue to cyan (temperature/load themes)warm- Yellow to orange to red (heat/pressure themes)spectrum- Blue to green to yellow to red (full range)grayscale- Grey11 to grey100 (monochrome)
Custom Gradients:
- Two colors:
blue→red - Multiple colors:
blue→green→yellow→red
Example:
row: [progress:75] # Default threshold colors
row: [progress:45:inline] # Default with explicit style
row: [progress:90:warm] # Warm gradient
row: [progress:75:cool:inline] # Cool gradient with style
row: [progress:60:blue→green→red] # Custom gradient
Displays an inline mini-graph using smooth Unicode block characters for compact trend visualization.
[sparkline:VALUES]
[sparkline:VALUES:COLOR]
[sparkline:VALUES:COLOR:WIDTH]
[sparkline:VALUES:GRADIENT]
[sparkline:VALUES:GRADIENT:WIDTH]
- VALUES: Comma-separated numbers (e.g.,
10,20,15,25,30) - COLOR: Optional Spectre.Console color (default:
grey70) - GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- WIDTH: Optional character width (default: 30, pads with background if data points < width)
Character Set: Uses Unicode block characters (▁▂▃▄▅▆▇█) for smooth visualization
Predefined Gradients:
cool,warm,spectrum,grayscale
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: Load trend: [sparkline:45,48,52,55,50,53:green] # Solid color, default width (30)
row: Memory: [sparkline:60,62,65,70,68,72,75:warm] 75% # Warm gradient, default width
row: CPU: [sparkline:10,20,30,40,50:blue→red] # Custom gradient, default width
row: Traffic: [sparkline:5,10,15,20:green:15] # Narrow sparkline (15 chars)
Displays a compact inline progress indicator with optional gradient coloring.
[miniprogress:VALUE]
[miniprogress:VALUE:WIDTH]
[miniprogress:VALUE:WIDTH:GRADIENT]
- VALUE: Integer 0-100
- WIDTH: Character width 3-20 (default: 10)
- GRADIENT: Optional gradient name or custom gradient (see Gradients section)
Default Behavior (no gradient specified):
- Green for 0-69%
- Yellow for 70-89%
- Red for 90-100%
Predefined Gradients:
cool,warm,spectrum,grayscale
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: CPU: [miniprogress:75] 75% # Default threshold colors
row: RAM: [miniprogress:85:15] 85% # Default with custom width
row: Temp: [miniprogress:60:12:cool] 60°C # Cool gradient
row: Load: [miniprogress:90:10:warm] # Warm gradient
row: Disk: [miniprogress:45:15:blue→green→red] # Custom gradient
Displays structured data in aligned columns. Table directives are used without the row: prefix.
[table:HEADER1|HEADER2|HEADER3]
[tablerow:VALUE1|VALUE2|VALUE3]
[tablerow:VALUE1|VALUE2|VALUE3]
- Headers: Pipe-separated column names
- Rows: Pipe-separated values (supports Spectre.Console markup)
Example:
[table:Service|Status|Memory]
[tablerow:nginx|[green]running[/]|45MB]
[tablerow:mysql|[green]running[/]|320MB]
[tablerow:redis|[yellow]warning[/]|89MB]
Displays a full-width horizontal line to separate sections.
[divider]
[divider:CHARACTER]
[divider:CHARACTER:COLOR]
- CHARACTER: Single character (default:
─) - COLOR: Optional Spectre.Console color (default:
grey70)
Example:
row: [bold]Section 1[/]
row: Content here...
row: [divider]
row: [bold]Section 2[/]
row: More content...
row: [divider:═:cyan1]
Displays a smooth block character chart for data visualization (4 lines tall) with optional gradient coloring.
[graph:VALUES]
[graph:VALUES:COLOR]
[graph:VALUES:COLOR:LABEL]
[graph:VALUES:COLOR:LABEL:MIN-MAX]
[graph:VALUES:COLOR:LABEL:MIN-MAX:WIDTH]
[graph:VALUES:GRADIENT]
[graph:VALUES:GRADIENT:LABEL]
[graph:VALUES:GRADIENT:LABEL:MIN-MAX]
[graph:VALUES:GRADIENT:LABEL:MIN-MAX:WIDTH]
- VALUES: Comma-separated numbers
- COLOR: Optional Spectre.Console color (default:
cyan1) - GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- LABEL: Optional label text
- MIN-MAX: Optional fixed scale range (e.g.,
0-100for percentage graphs) - WIDTH: Optional character width (default: 30, pads with background if data points < width)
Character Set: Uses Unicode block characters (▁▂▃▄▅▆▇█) for smooth visualization with 36 vertical levels (4 rows × 9 levels)
Predefined Gradients:
cool- Blue to cyan (temperature/load themes)warm- Yellow to orange to red (heat/pressure themes)spectrum- Blue to green to yellow to red (full range)grayscale- Grey11 to grey100 (monochrome)
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: [graph:10,20,15,25,30,28,35,40:green:CPU Load] # Solid color, default width (30)
row: [graph:60,62,65,70,68,72,75,78:warm:Temperature] # Warm gradient
row: [graph:5,10,15,20,25,30:blue→red:Load] # Custom gradient
row: [graph:45,50,55,60,58,62:cool] # Cool gradient no label
row: [graph:45,50,62,58,65,70:cool:CPU %:0-100] # Fixed 0-100% scale
row: [graph:10,15,20,25:green:Load:0-100:40] # Custom width (40 chars)
Displays a smooth line chart with connected points for time-series visualization. Supports both Braille characters (default, smooth) and ASCII style rendering.
[line:VALUES:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT]
[line:VALUES:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT:STYLE]
[line:VALUES:GRADIENT:LABEL:MIN-MAX:WIDTH:HEIGHT]
[line:VALUES:GRADIENT:LABEL:MIN-MAX:WIDTH:HEIGHT:STYLE]
- VALUES: Comma-separated numbers (e.g.,
10,20,15,30,25) - COLOR: Optional Spectre.Console color (default:
cyan1) - GRADIENT: Optional gradient name or custom gradient (see Gradients section)
- LABEL: Optional label text displayed above the graph
- MIN-MAX: Optional fixed scale range (e.g.,
0-100for percentage graphs) - WIDTH: Character width of the graph (default: 60)
- HEIGHT: Character height of the graph (default: 8)
- STYLE: Optional rendering style -
braille(default, smooth) orascii(block characters)
Rendering Styles:
- braille (default): Uses Braille Unicode characters for smooth, high-resolution line rendering
- ascii: Uses block characters (█, ▀, ▄) for a more traditional ASCII art look
Predefined Gradients:
cool- Blue to cyan (temperature/load themes)warm- Yellow to orange to red (heat/pressure themes)spectrum- Blue to green to yellow to red (full range)grayscale- Grey11 to grey100 (monochrome)
Custom Gradients:
blue→red,green→yellow→red, etc.
Example:
row: [line:10,20,15,30,25,35,40,30,20,10:cyan:Sample Data:0-50:60:8] # Braille style (default)
row: [line:10,20,15,30,25,35,40,30,20,10:cyan:Sample Data:0-50:60:8:ascii] # ASCII style
row: [line:5,15,10,25,20,35,30,45,40,50:warm:CPU Usage:0-100:50:6] # Warm gradient, compact
row: [line:10,15,20,25,30,25,20,15,10:blue:Memory:0-40:40:4] # Small graph
row: [line:25,25,25,25,25:grey70:Constant:0-50:30:4] # Flat line
ServerHub includes a built-in time-series storage system that allows widgets to persist data and query historical metrics. These elements interact with the storage backend.
Persists metric data to the SQLite database using InfluxDB-style line protocol.
datastore: MEASUREMENT[,tag=val,tag=val] field=val[,field=val] [timestamp]
- MEASUREMENT: Required alphanumeric identifier (e.g.,
cpu_usage,memory_used) - tags: Optional comma-separated key=value pairs for grouping (e.g.,
core=0,host=srv01) - fields: Required comma-separated key=value pairs (numeric, boolean, or quoted strings)
- timestamp: Optional Unix timestamp in seconds (auto-generated if omitted)
Examples:
# Simple metric
echo "datastore: cpu_usage value=75.5"
# With tags
echo "datastore: cpu_usage,core=0,host=srv01 value=75.5,temp=65"
# Multiple fields
echo "datastore: disk_io,device=sda reads=1500,writes=2300"
# Explicit timestamp
echo "datastore: metric,tag=x value=100 1707348000"Note: Data is automatically scoped to the widget ID. Each widget's data is isolated in the database.
Retrieves and displays a single value from stored data.
[datafetch:KEY]
[datafetch:KEY:AGGREGATION:TIMERANGE]
- KEY: Measurement key in format
measurement.field(e.g.,cpu_usage.value) - AGGREGATION:
latest(default),avg,max,min,sum,count - TIMERANGE: Time range like
30s,5m,1h,24h,7d, orlast_10
Examples:
# Latest value
echo "row: CPU: [datafetch:cpu_usage.value]%"
# Average over last 30 seconds
echo "row: 30s avg: [datafetch:cpu_usage.value:avg:30s]%"
# Maximum over 1 hour
echo "row: Peak (1h): [datafetch:cpu_usage.value:max:1h]%"
# Count of samples
echo "row: Samples: [datafetch:cpu_usage.value:count:1h]"If no data is available, renders as --.
Renders stored time-series data as a vertical bar chart (4 lines tall).
[history_graph:KEY:TIMERANGE]
[history_graph:KEY:TIMERANGE:COLOR]
[history_graph:KEY:TIMERANGE:COLOR:LABEL]
[history_graph:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX]
[history_graph:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX:WIDTH]
- KEY: Measurement key (e.g.,
cpu_usage.value) - TIMERANGE: Time range to query (e.g.,
30s,1m,1h,24h) - COLOR/GRADIENT: Optional color or gradient name
- LABEL: Optional label text
- MIN-MAX: Optional fixed scale (e.g.,
0-100) - WIDTH: Character width (default: 30)
Examples:
# Short-term (last 60 seconds)
echo "row: [history_graph:cpu_usage.value:60s:cool:Load %:0-100]"
# Medium-term (last hour)
echo "row: [history_graph:cpu_usage.value:1h:cyan:CPU Load:0-100:40]"
# Long-term (24 hours)
echo "row: [history_graph:cpu_usage.value:24h:warm:Temperature:0-100]"Renders stored time-series data as an inline sparkline.
[history_sparkline:KEY:TIMERANGE]
[history_sparkline:KEY:TIMERANGE:COLOR]
[history_sparkline:KEY:TIMERANGE:COLOR:WIDTH]
- KEY: Measurement key (e.g.,
cpu_usage.value) - TIMERANGE: Time range to query
- COLOR/GRADIENT: Optional color or gradient name
- WIDTH: Character width (default: 30)
Examples:
# Inline trend for last 30 seconds
echo "row: CPU trend: [history_sparkline:cpu_usage.value:30s:cool:20]"
# Memory trend over 5 minutes
echo "row: Mem: [history_sparkline:memory.used:5m:warm:25]"Renders stored time-series data as a smooth line chart.
[history_line:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT]
[history_line:KEY:TIMERANGE:COLOR:LABEL:MIN-MAX:WIDTH:HEIGHT:STYLE]
- KEY: Measurement key (e.g.,
cpu_usage.value) - TIMERANGE: Time range to query (e.g.,
5m,1h,24h) - COLOR/GRADIENT: Optional color or gradient name
- LABEL: Optional label text
- MIN-MAX: Optional fixed scale (e.g.,
0-100) - WIDTH: Character width (default: 60)
- HEIGHT: Character height (default: 8)
- STYLE:
braille(default) orascii
Examples:
# 1 minute history
echo "row: [history_line:cpu_usage.value:1m:cyan:CPU:0-100:60:8:braille]"
# 5 minute history with gradient
echo "row: [history_line:cpu_usage.value:5m:warm:Load:0-100:50:6:braille]"
# 24 hour history
echo "row: [history_line:memory.used:24h:cool:Memory:0-100:80:10:braille]"All storage elements support these time range formats:
- Seconds:
10s,30s,60s - Minutes:
1m,5m,15m,30m - Hours:
1h,6h,12h,24h - Days:
7d,30d - Samples:
last_10,last_30,last_100
#!/bin/bash
echo "title: CPU Monitor"
echo "refresh: 5"
# Get current CPU load
CPU_LOAD=$(awk '{print $1}' /proc/loadavg)
CPU_PERCENT=$(awk "BEGIN {printf \"%.0f\", ($CPU_LOAD / $(nproc)) * 100}")
# Store the metric
echo "datastore: cpu_usage,host=$(hostname) value=$CPU_PERCENT"
# Display current + historical data
echo "row: [status:ok] Current: ${CPU_PERCENT}%"
echo "row: Latest: [datafetch:cpu_usage.value] | 30s avg: [datafetch:cpu_usage.value:avg:30s]"
echo "row: Trend: [history_sparkline:cpu_usage.value:1m:cool:20]"
echo "row: "
echo "row: [history_graph:cpu_usage.value:1m:cool:Last Minute:0-100:40]"
# Extended mode: longer history
if [[ "$1" == "--extended" ]]; then
echo "row: "
echo "row: [bold]24 Hour History[/]"
echo "row: [history_line:cpu_usage.value:24h:warm:CPU Load:0-100:80:12:braille]"
fiRow content supports Spectre.Console markup for styling:
[red]Error text[/]
[green]Success text[/]
[yellow]Warning text[/]
[blue]Info text[/]
[cyan1]Accent text[/]
[grey70]Muted text[/]
[bold]Bold text[/]
[italic]Italic text[/]
[underline]Underlined text[/]
[dim]Dimmed text[/]
[bold red]Bold red text[/]
[grey70 italic]Muted italic[/]
#!/bin/bash
# my-widget.sh - Example custom widget with rich elements
echo "title: System Monitor"
echo "refresh: 5"
# Status with sparkline trend
cpu_history="45,48,52,55,50,53,58,60,57,62"
echo "row: CPU: 62% [sparkline:${cpu_history}:green]"
echo "row: [miniprogress:62:15] Current load"
# Divider
echo "row: [divider]"
# Table for services
echo "row: [bold]Services[/]"
echo "[table:Name|Status|Memory]"
echo "[tablerow:nginx|[green]running[/]|45MB]"
echo "[tablerow:mysql|[green]running[/]|320MB]"
# Divider
echo "row: [divider:─:cyan1]"
# Graph for extended mode
if [[ "$1" == "--extended" ]]; then
memory_history="60,62,65,70,68,72,75,78,80,85"
echo "row: [graph:${memory_history}:yellow:Memory usage (10 samples)]"
fiServerHub enforces mandatory checksum validation to protect against widget tampering.
| Source | Checksum Location | Trust Level | Bypass with --dev-mode? |
|---|---|---|---|
| Bundled | Hardcoded at build | Highest (maintainer reviewed) | No - always validated |
| Config sha256 | User adds to YAML | High (user verified) | Yes |
| --discover | Captured at approval | High (user reviewed code) | N/A |
Checksums must come from a trusted source at a trusted moment:
- Bundled: Build-time (maintainer reviewed code)
- Third-party: Author provides checksum, user verifies
- Self-developed: Author calculates after code is finalized
- --discover: Captured after user reviews code preview
Checksums should NOT be auto-generated from current file state (attacker could modify then regenerate).
All custom widgets (in ~/.config/serverhub/widgets/) require SHA256 checksum validation:
widgets:
my-widget:
path: my-widget.sh
sha256: a1b2c3d4e5f6... # REQUIRED for custom widgets
refresh: 10Without a checksum, custom widgets will fail to load with a helpful error showing the required checksum.
Option 1: Discovery (Recommended)
# Discover and add new widgets interactively
serverhub --discover
# Review code preview → approve → auto-added with checksumOption 2: Manual
# Calculate checksum
sha256sum ~/.config/serverhub/widgets/my-widget.sh
# Add to config.yaml with checksum# Verify all widget checksums (shows VALID/MISMATCH/MISSING)
serverhub --verify-checksumsFor widget development, use --dev-mode to skip custom widget checksum validation:
# Skip checksum validation for custom widgets only
serverhub --dev-mode --widgets-path ./my-dev-widgetsImportant:
- Bundled widgets are always validated, even in dev mode
- Dev mode shows prominent warnings (status bar, orange border, startup dialog)
- Never use --dev-mode in production
| Validation | Normal Mode | --dev-mode |
|---|---|---|
| Path restrictions | Enforced | Enforced |
| Symlink detection | Enforced | Enforced |
| Executable check | Enforced | Enforced |
| Bundled checksum | Enforced | Enforced |
| Custom widget checksum | Enforced | Skipped |
Bundled widgets (in ~/.local/share/serverhub/widgets/) are pre-validated at build time with hardcoded checksums and don't require checksums in your config.
Third-Party Widget:
# 1. Download widget
wget https://example.com/monitoring.sh
# 2. Verify author's checksum
sha256sum monitoring.sh
# Compare with author's published checksum
# 3. Move to widgets directory
mv monitoring.sh ~/.config/serverhub/widgets/
# 4. Discover and add
serverhub --discoverSelf-Developed Widget:
# 1. Develop with --dev-mode
serverhub --dev-mode --widgets-path ./my-widgets
# 2. When done, add via --discover
cp my-widgets/custom.sh ~/.config/serverhub/widgets/
serverhub --discover
# 3. Run normally
serverhubWhen a user opens the expanded view (double-click or Enter on a widget), the script is re-executed with the --extended argument. This allows scripts to output additional detail that wouldn't fit in the dashboard view.
Check for --extended in your script arguments:
#!/bin/bash
EXTENDED=false
if [[ "$1" == "--extended" ]]; then
EXTENDED=true
fiecho "title: My Widget"
echo "refresh: 5"
# Always show summary
echo "row: [status:ok] Service running"
# Show additional detail only in extended mode
if [ "$EXTENDED" = true ]; then
echo "row: "
echo "row: [bold]Extended Details:[/]"
echo "row: PID: 12345"
echo "row: Uptime: 5 days"
echo "row: Memory: 256MB"
# ... more detailed info
fi- Dashboard view: Keep output concise (fits within
max_lines) - Extended view: Include full details, logs, stats, etc.
- Scripts without
--extendedhandling work normally (same output in both views) - Extended mode is optional - scripts don't need to handle it
- Scripts must be executable (
chmod +x) - Output to stdout only
- One protocol element per line
- Exit code 0 for success (non-zero shows error state)
- Keep execution time short (< refresh interval)