Widget Development Guide

February 8, 2026 · View on GitHub

Complete guide for creating, testing, and deploying custom ServerHub widgets.

Table of Contents

Quick Start

The fastest way to create and deploy a working widget:

# 1. Create widget from template
serverhub new-widget bash-basic --name my-monitor

# 2. Make it executable
chmod +x my-monitor.sh

# 3. Test it
serverhub test-widget my-monitor.sh --yes

# 4. Customize the monitoring logic (edit the file)

# 5. Test again
serverhub test-widget my-monitor.sh --yes

# 6. Add to dashboard
serverhub --discover

That's it! Your widget is now in the dashboard.

Creating Widgets from Templates

Templates provide a complete widget structure with best practices and example code.

Interactive Widget Creation

The interactive wizard guides you through the process:

serverhub new-widget
  1. Select a template from the list
  2. Fill in variables (with examples and defaults)
  3. Choose output file path
  4. Preview the generated content (optional)
  5. Confirm creation

Non-Interactive Creation

Specify all options on the command line:

# Basic usage
serverhub new-widget bash-basic --name my-widget

# With custom variables
serverhub new-widget bash-basic \
  --name cpu-monitor \
  --title "CPU Monitor" \
  --refresh 10 \
  --author "Jane Smith"

# With custom output path
serverhub new-widget python-basic \
  --name api-checker \
  --output ~/custom-widgets/api-checker.py

List Available Templates

serverhub new-widget list

Template Reference

Bash Templates

bash-basic

Simple shell script with status indicators and actions.

Difficulty: Beginner Best for: Quick monitoring scripts, simple checks, getting started

Features:

  • Basic widget protocol implementation
  • Status indicators
  • Configurable refresh interval
  • Action buttons

Example:

serverhub new-widget bash-basic --name disk-monitor --refresh 10

bash-advanced

Advanced shell script with sparklines, graphs, tables, and history tracking.

Difficulty: Intermediate Best for: Complex monitoring, trending data, detailed statistics

Features:

  • Dashboard vs Extended mode support
  • History tracking with configurable cache
  • Sparklines for trend visualization
  • Graphs in extended mode
  • Statistical tables
  • Dynamic actions

Example:

serverhub new-widget bash-advanced --name system-monitor --max-samples 50

Python Templates

python-basic

Basic Python widget with protocol implementation.

Difficulty: Beginner Best for: Python developers, API monitoring, complex logic

Features:

  • Basic widget protocol in Python
  • Easy to extend with Python libraries
  • Status indicators and actions
  • No external dependencies required

Example:

serverhub new-widget python-basic --name network-monitor --refresh 5

python-advanced

Python widget with rich visualizations and error handling.

Difficulty: Advanced Best for: Complex monitoring, data visualization, production use

Features:

  • Dashboard vs Extended mode support
  • Sparklines and graphs
  • Statistical tables
  • Built-in error handling
  • Easy integration with Python ecosystem

Example:

serverhub new-widget python-advanced --name api-monitor

Other Templates

csharp-script

C# widget using dotnet-script (no compilation required).

Difficulty: Intermediate Best for: .NET developers, leveraging existing C# libraries

Prerequisites:

dotnet tool install -g dotnet-script

Example:

serverhub new-widget csharp-script --name service-monitor

powershell

Cross-platform PowerShell widget.

Difficulty: Beginner Best for: Windows admins, cross-platform PowerShell users

Prerequisites: PowerShell Core installed

Example:

serverhub new-widget powershell --name process-monitor

Template Variables

All templates support these common variables:

VariableRequiredDescriptionExample
WIDGET_NAMEYesWidget identifier (lowercase, hyphens, no spaces)cpu-monitor
WIDGET_TITLENoDisplay title shown in dashboardCPU Monitor
REFRESH_INTERVALNoRefresh interval in seconds (default: 5)10
AUTHORNoWidget author nameJohn Doe
DESCRIPTIONNoWidget descriptionMonitors CPU usage

Advanced templates may have additional variables (e.g., CACHE_FILE, MAX_SAMPLES).

Customizing Your Widget

After creating a widget from a template, customize it to monitor what you need.

Replace TODO Sections

Templates include TODO comments showing where to add your monitoring logic:

# TODO: Add your monitoring logic here
echo "row: [status:ok] Status: Operational"

Example Customizations

Bash - CPU Usage:

CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print \$2}' | cut -d'%' -f1)

if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then
    echo "row: [status:error] CPU: ${CPU_USAGE}%"
else
    echo "row: [status:ok] CPU: ${CPU_USAGE}%"
fi

Python - Memory Usage:

import psutil

mem = psutil.virtual_memory()
status = "error" if mem.percent > 80 else "ok"
print(f"row: [status:{status}] Memory: {mem.percent}%")

Status Indicators

Use status indicators to show health:

echo "row: [status:ok] Everything is fine"
echo "row: [status:warning] Check this"
echo "row: [status:error] Critical issue"

Graphs and Sparklines

Advanced templates support data visualization:

# Sparkline (compact trend)
echo "row: [sparkline:10,20,30,40,50,40,30,20:green]"

# Multi-line graph (4 rows tall)
echo "row: [graph:10,20,30,40,50,40,30,20:cyan:CPU Load:0-100:30]"

# Line graph (smooth connected line)
echo "row: [line:10,20,15,30,25,35,40,30,20,10:cyan:Temperature:0-50:60:8]"
echo "row: [line:5,15,25,35,45:warm:CPU History:0-100:50:6:braille]"  # Braille style
echo "row: [line:5,15,25,35,45:blue:Memory:0-100:50:6:ascii]"          # ASCII style

Historical Data and Storage System

Recommended approach for tracking historical data:

ServerHub includes a powerful SQLite-based storage system for persistent time-series data. This is the recommended way to track historical metrics in your widgets.

Benefits over manual cache files:

  • ✅ Persistent storage (survives cache clears and reboots)
  • ✅ Automatic cleanup (configurable retention policy, default 30 days)
  • ✅ Flexible time ranges (sample-based or time-based queries)
  • ✅ Rich aggregations (avg, max, min, sum, count)
  • ✅ Multiple visualization options
  • ✅ No manual file management needed
  • ✅ Centralized database for all widgets

How to use it:

  1. Store data using datastore: directives (InfluxDB line protocol):
# Simple measurement
echo "datastore: cpu_load value=$load_percent"

# Multiple fields
echo "datastore: system_stats load=$load,temp=$temp,uptime=$uptime"

# With tags for grouping
echo "datastore: disk_usage,mount=$mount value=$pct"
echo "datastore: network_traffic,interface=$iface rx_kb=$rx,tx_kb=$tx"
echo "datastore: docker_stats,container=$name cpu_pct=$cpu,mem_pct=$mem"
  1. Visualize historical data using storage-based inline elements:
# Dashboard mode: Show last N samples
echo "row: [history_graph:cpu_load.value:last_30:cool:Load %:0-100:40]"
echo "row: [history_sparkline:network_traffic.rx_kb:last_10:green:15]"

# Extended mode: Show time-based ranges
echo "row: [history_graph:cpu_load.value:1h:warm:CPU (1h):0-100:60]"
echo "row: [history_line:memory_usage.value:24h:spectrum:Memory (24h):0-100:80:12:braille]"

# With tag filtering
echo "row: [history_graph:disk_usage.value,mount=/:last_30:warm:Root Usage:0-100:40]"
echo "row: [history_graph:docker_stats.cpu_pct,container=web:last_30:spectrum:Web CPU:0-100:40]"

Time ranges:

  • Sample-based: last_10, last_30, last_100, etc.
  • Time-based: 10s, 30s, 1m, 5m, 15m, 1h, 6h, 12h, 24h, 7d, 30d

Storage Configuration:

Storage is enabled by default in ~/.config/serverhub/config.yaml:

storage:
  enabled: true
  database_path: ~/.config/serverhub/serverhub.db
  retention_days: 30
  cleanup_interval_hours: 1
  max_database_size_mb: 500
  auto_vacuum: true

Migration from cache files:

If you have existing widgets using cache files (e.g., ~/.cache/serverhub/widget-data.txt), migrate to the storage system:

# OLD: Manual cache file approach (not recommended)
CACHE_DIR="$HOME/.cache/serverhub"
echo "$value" >> "$CACHE_DIR/widget-data.txt"
tail -n 30 "$CACHE_DIR/widget-data.txt" > "$CACHE_DIR/widget-data.tmp"
mv "$CACHE_DIR/widget-data.tmp" "$CACHE_DIR/widget-data.txt"
history=$(paste -sd',' "$CACHE_DIR/widget-data.txt")
echo "row: [graph:${history}:cyan:History:0-100]"

# NEW: Storage system (recommended)
echo "datastore: widget_metric value=$value"
echo "row: [history_graph:widget_metric.value:last_30:cyan:History:0-100:40]"

See also: All bundled widgets (cpu, memory, disk, network, docker) use the storage system as reference examples.

Tables

Create structured data displays:

echo "row: table:Header1|Header2|Header3"
echo "row: table:Value1|Value2|Value3"
echo "row: table:Data1|Data2|Data3"

Actions

Add interactive actions to your widget:

echo "action: Restart Service:sudo systemctl restart myservice"
echo "action: Clear Cache:rm -rf /tmp/cache/*"
echo "action: Refresh:bash /path/to/widget.sh"

Dashboard vs Extended Mode

Advanced templates detect extended mode automatically:

EXTENDED_MODE=false
if [[ "\$1" == "--extended" ]]; then
    EXTENDED_MODE=true
fi

if [[ "$EXTENDED_MODE" == "false" ]]; then
    # Show compact view
    echo "row: sparkline:${HISTORY[*]}"
else
    # Show detailed view
    echo "row: graph:${HISTORY[*]}"
    echo "row: table:Metric|Value"
fi

Testing Widgets

ServerHub provides comprehensive testing to validate widget scripts before deployment.

Quick Test

serverhub test-widget mywidget.sh

The command will:

  1. Show a security warning
  2. Ask for confirmation to execute
  3. Run the widget script
  4. Display detailed analysis and validation results

Test Options

OptionDescription
--extendedPass --extended flag to the widget (tests expanded view mode)
--yes, -ySkip confirmation prompt (useful for automation/CI)
--uiLaunch UI preview mode (interactive rendering)

Test Examples

# Interactive test with confirmation
serverhub test-widget mywidget.sh

# Test extended mode
serverhub test-widget mywidget.sh --extended

# Skip confirmation (automation/CI)
serverhub test-widget mywidget.sh --yes

# Test both modes in sequence
serverhub test-widget mywidget.sh --yes && \
serverhub test-widget mywidget.sh --extended --yes

# UI preview mode (interactive)
serverhub test-widget mywidget.sh --ui

Test Output

The test command provides comprehensive analysis:

1. Execution Status

  • Exit code validation
  • Execution time measurement
  • Error detection
Execution Status:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ Widget executed successfully
✓ Execution time: 28ms

2. Parsed Output

  • Title and refresh interval extraction
  • Row parsing with status indicators
  • Detection of protocol elements (progress bars, sparklines, graphs, tables)
Parsed Output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Title: System Monitor
  Refresh: 5 seconds

  Rows (4):
    OK System is operational
    INFO CPU usage normal

      PROGRESS: 75%
      SPARKLINE: 10 values

3. Actions

  • Action syntax validation
  • Label and command presence checks
  • Flag detection (danger, refresh, sudo, timeout)
Actions (2):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ✓ Restart Service
    Command: systemctl restart myservice
  ✓ Force Restart (danger, sudo)
    Command: systemctl restart --force myservice

4. Protocol Validation

  • Markup syntax errors
  • Invalid status values
  • Malformed protocol elements
Protocol Validation:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ✓ No protocol errors
  ✓ All markup valid

5. Warnings

  • Missing health checks
  • No interactive actions
  • Insufficient data rows
Warnings:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  ⚠ No error status indicator found (consider adding health checks)
  ⚠ No actions defined (consider adding interactive actions)

6. Suggestions

  • Recommended protocol features
  • Best practices
  • Enhancement opportunities
Suggestions:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  → Consider adding sparklines or graphs for trends
  → Consider using progress bars for percentage values
  → Consider using tables for structured data

Exit Codes

  • 0 - Widget passed all tests
  • 1 - Widget failed (execution error, protocol errors, or action validation errors)

Security Considerations

The test command executes scripts with minimal security restrictions to allow testing from any location (including /tmp or development directories).

Before testing:

  • Only test widgets from trusted sources
  • Review the widget code before testing
  • Understand what the script will do

Use --yes to skip the confirmation prompt in trusted environments (CI/CD).

Deploying Widgets

After creating and testing your widget, deploy it to your dashboard.

Step 1: Place Widget in Custom Directory

# Copy to custom widgets directory
cp my-widget.sh ~/.config/serverhub/widgets/

# Or create widgets in place
cd ~/.config/serverhub/widgets/
serverhub new-widget bash-basic --name my-widget

Step 2: Discover and Add to Config

serverhub --discover

This will:

  • Find your new widget
  • Calculate its SHA256 checksum
  • Add it to your config file with proper security
  • Prompt for confirmation

Step 3: Launch Dashboard

serverhub

Your new widget appears in the dashboard!

Manual Configuration

Alternatively, manually add to ~/.config/serverhub/config.yaml:

widgets:
  my-widget:
    path: my-widget.sh
    location: custom
    sha256: a1b2c3d4e5f6...  # Required!
    refresh: 10
    timeout: 30

Calculate checksum:

sha256sum ~/.config/serverhub/widgets/my-widget.sh

CI/CD Integration

Exit Code Testing

The test command returns proper exit codes for integration with CI/CD pipelines:

#!/bin/bash
# Pre-deployment widget validation

for widget in widgets/*.sh; do
    echo "Testing $widget..."
    if ! serverhub test-widget "$widget" --yes; then
        echo "❌ Widget test failed: $widget"
        exit 1
    fi
done

echo "✓ All widgets passed validation"

GitHub Actions Example

name: Widget Testing

on: [push, pull_request]

jobs:
  test-widgets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Install ServerHub
        run: |
          curl -fsSL https://raw.githubusercontent.com/nickprotop/ServerHub/main/install.sh | bash

      - name: Test Widgets
        run: |
          for widget in widgets/*.sh; do
            serverhub test-widget "$widget" --yes || exit 1
          done

Pre-commit Hook Example

Create .git/hooks/pre-commit:

#!/bin/bash
# Test modified widget files before commit

WIDGETS=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$' | grep 'widgets/')

if [ -n "$WIDGETS" ]; then
    echo "Testing modified widgets..."
    for widget in $WIDGETS; do
        if [ -f "$widget" ]; then
            echo "  Testing $widget..."
            if ! serverhub test-widget "$widget" --yes; then
                echo "❌ Widget test failed: $widget"
                echo "Fix the widget or use 'git commit --no-verify' to bypass"
                exit 1
            fi
        fi
    done
    echo "✓ All widgets passed validation"
fi

Protocol Reference

For complete details on the widget protocol, see WIDGET_PROTOCOL.md.

Quick Protocol Summary

Widget output structure:

echo "title: Widget Title"
echo "refresh: 5"
echo "row: Text content"
echo "row: [status:ok] Status with indicator"
echo "row: [progress:75]"
echo "row: sparkline:10 20 30 40"
echo "row: table:Col1|Col2"
echo "action: Label:command"

Status indicators:

  • [status:ok] - Green checkmark
  • [status:warning] - Yellow warning
  • [status:error] - Red error

Visualizations:

  • [progress:N] or [progress:N:inline] - Progress bars
  • sparkline:values - Compact trend line
  • graph:values - Full ASCII graph (extended mode)
  • table:col1|col2|col3 - Table data

Actions:

echo "action: Label:command"
echo "action: Danger Action:command:danger"
echo "action: With Sudo:command:sudo"
echo "action: With Timeout:command:timeout=60"

Development Workflow

Recommended workflow for widget development:

# 1. Create from template
serverhub new-widget bash-basic --name my-widget

# 2. Make executable
chmod +x my-widget.sh

# 3. Test initial template
serverhub test-widget my-widget.sh --yes

# 4. Edit and customize
nano my-widget.sh

# 5. Test changes
serverhub test-widget my-widget.sh --yes

# 6. Test extended mode (if applicable)
serverhub test-widget my-widget.sh --extended --yes

# 7. Deploy to dashboard
mv my-widget.sh ~/.config/serverhub/widgets/
serverhub --discover

# 8. Launch and verify
serverhub

Troubleshooting

Template fetch fails

Error: Failed to fetch template index from GitHub

Solution: Check your internet connection. Templates are fetched from GitHub on demand.

Widget doesn't execute

After creating a widget, if it doesn't run:

  1. Make sure it's executable: chmod +x widget.sh
  2. Test it directly: ./widget.sh
  3. Check syntax errors in your customizations
  4. Use serverhub test-widget widget.sh to validate

Variables not substituted

If you see {{VARIABLE}} in your widget output:

  1. Check the template was created correctly
  2. Verify you provided values for required variables
  3. The template system runs at creation time, not runtime

Test failures

If your widget fails tests:

  1. Review the Protocol Validation section of the test output
  2. Check for malformed action syntax (missing labels, commands)
  3. Verify all protocol elements are correctly formatted
  4. Look at Suggestions section for improvement ideas

Examples

Example 1: Simple CPU Monitor

#!/bin/bash
echo "title: CPU Monitor"
echo "refresh: 5"

CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print \$2}' | cut -d'%' -f1 | cut -d'.' -f1)

if (( CPU_USAGE > 80 )); then
    echo "row: [status:error] CPU: ${CPU_USAGE}%"
elif (( CPU_USAGE > 50 )); then
    echo "row: [status:warning] CPU: ${CPU_USAGE}%"
else
    echo "row: [status:ok] CPU: ${CPU_USAGE}%"
fi

echo "row: [progress:${CPU_USAGE}]"
echo "action: View Top:top -b -n 1 | head -20"

Example 2: Service Monitor with Actions

#!/bin/bash
echo "title: Service Monitor"
echo "refresh: 10"

SERVICE="nginx"

if systemctl is-active --quiet "$SERVICE"; then
    echo "row: [status:ok] $SERVICE is running"
    echo "action: Stop Service:sudo systemctl stop $SERVICE:danger,sudo"
    echo "action: Restart:sudo systemctl restart $SERVICE:sudo"
else
    echo "row: [status:error] $SERVICE is stopped"
    echo "action: Start Service:sudo systemctl start $SERVICE:sudo"
fi

echo "action: View Logs:journalctl -u $SERVICE -n 50"

Example 3: Python API Monitor

#!/usr/bin/env python3
import requests

print("title: API Monitor")
print("refresh: 30")

try:
    response = requests.get("https://api.example.com/health", timeout=5)
    if response.status_code == 200:
        print("row: [status:ok] API is healthy")
        print(f"row: Response time: {response.elapsed.total_seconds():.2f}s")
    else:
        print(f"row: [status:error] API returned {response.status_code}")
except Exception as e:
    print(f"row: [status:error] API unreachable: {str(e)}")

print("action: Test API:curl -v https://api.example.com/health")

See Also