Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stylus Documentation Home

Stylus (style + status) is a lightweight status page for home infrastructure.

Configure a set of scripts in bash, Python or any other language that test the various parts of your infrastructure, set up HTML/SVG with a diagram of your network, and Stylus will generate you a dynamic stylesheet to give you a visual overview of the current state.

If you'd like to learn more about Stylus, you can read how it works.

Screenshots

These are some examples of what you can do with Stylus:

Using the IsoFlow library:

Stylus Screenshot

Using the d3.js library:

Stylus Screenshot

Using an SVG diagram:

Stylus Screenshot

Sections

What is Stylus?

Stylus is (1) a service/network monitoring application, and (2) a webserver with special endpoints.

Web Application

Included in the Stylus package is a built-in webapp that includes a number of visualizations, but you can also use it to serve any content you like, even fully replacing the built-in webapp.

The webapp includes an iframe visualization that can be used to display any URL you like, and if you're serving a local HTML page, can even inject CSS into it for dynamic updates without scripting.

Status Monitoring

The status monitoring portion is based around scripts, written in any shell scripting language you like. Each script is run regularly at an interval, and if the script returns 0 that is considered "up" for a given service. If the service times out, or returns a non-zero error this is considered a soft ("yellow") or hard ("red") failure.

Web Server

The special endpoints available on the webserver are:

  • /style.css: A dynamically generated CSS file based on the current
  • /status.json: A JSON representation of the current state

The style.css endpoint may be linked by a HTML or SVG file served from the static directory that is configured. If desired, the HTML page can dynamically refresh the CSS periodically using Javascript. See the included examples for how this might work.

If you need more flexibility than CSS can provide, you can use the status.json endpoint to get the current status of the various services, and dynamically update HTML, SVG images, or potentially even use React.js to update a more complex and interactive page in real-time.

Stylus ships with a number of examples that can be found in the source bundle.

Copy the simple_network folder to a location of your choice. In this guide we will assume that it will appear in ~/stylus/.

Once you've got the example set up locally, we can walk though...

Running Stylus

You can run Stylus in several ways:

Docker

The recommended option is to use Docker. A multi-arch Docker container is available under the repository mmastrac/stylus at https://hub.docker.com/r/mmastrac/stylus/.

Note that the container is hard-wired to run against a configuration file located in the container at /srv/config.yaml, and assumes that the remainder of the configuration is located in subdirectories of /srv.

You should map the container's /srv directory to a local directory containing your configuration.

# Assume that this is running against the stylus example, this will map the example directory into
# the container's /srv folder. The container will automatically load config.yaml from this folder!
docker run --rm --name stylus -p 8000:8000 -v ~/stylus/:/srv mmastrac/stylus:latest init
docker run --rm --name stylus -p 8000:8000 -v ~/stylus/:/srv mmastrac/stylus:latest

Static Binaries

If you would like to run it from a static binary, you may find a number of pre-built binary releases at https://github.com/mmastrac/stylus/releases.

# This will run against the example in ~/stylus/
stylus_<arch> init ~/stylus/
stylus_<arch> run ~/stylus/

Cargo

For any platform where cargo is natively available, you can simply cargo install the stylus package.

cargo install stylus
stylus init ~/stylus/
stylus run ~/stylus/

From Source

If you have the source downloaded, you can run stylus directly from that source directory.

cargo run -- init ~/stylus/
cargo run -- run ~/stylus/

stylus init

Initialize a new Stylus project directory with default configuration and monitor setup.

Usage

stylus init [OPTIONS] <DIRECTORY>

Arguments

  • <DIRECTORY> - The directory to initialize

Options

  • -v, --verbose... - Pass multiple times to increase the level of verbosity (overwritten by STYLUS_LOG)
  • -h, --help - Print help

The stylus init command creates a new Stylus project directory with the following structure:

What Gets Created

<DIRECTORY>/
├── config.yaml          # Main configuration file
├── monitor.d/           # Monitor directory
│   └── monitor/         # Default monitor with id "monitor"
│       ├── config.yaml  # Monitor "monitor" configuration
│       └── test.sh      # Test script for "monitor"
└── static/              # Static files directory
    └── README.md        # Placeholder for static files

Example

# Initialize a new Stylus project in ~/my-stylus
stylus init ~/my-stylus

After initialization, you can start the server with stylus run <DIRECTORY>.

stylus test

Runs the given test immediately and displays the status of the given monitor after it completes.

Usage

stylus test [OPTIONS] --monitor <MONITOR> <FILE>

Arguments

  • <DIRECTORY> - The configuration directory

Options

  • -m, --monitor <MONITOR> - The test to run
  • -v, --verbose... - Pass multiple times to increase the level of verbosity (overwritten by STYLUS_LOG)
  • -h, --help - Print help

The stylus test command runs a specific monitor test immediately and shows you the output without starting the full server. This is perfect for debugging monitor scripts or checking if your configuration works.

Example

# Test a monitor named "web-server"
stylus test --monitor web-server ~/my-stylus/

# Test with verbose output
stylus test -v --monitor web-server ~/my-stylus/

The command shows you three things:

  1. Monitor Log: What your script actually output
  2. State: How Stylus interpreted the results
  3. CSS: The CSS rules that would be generated
Monitor Log
-----------

2025-07-09T00:45:40.144844+00:00 [exec  ] Starting
2025-07-09T00:45:40.149592+00:00 [meta  ] status.metadata.rps="RPS: 443"
2025-07-09T00:45:40.149627+00:00 [stdout] Web server is responding normally
2025-07-09T00:45:40.149633+00:00 [stderr] + echo '@@STYLUS@@ status.metadata.rps="RPS: 443"'
2025-07-09T00:45:40.149638+00:00 [stderr] + '[' 5 -lt 8 ']'
2025-07-09T00:45:40.149643+00:00 [stderr] + echo 'Web server is responding normally'
2025-07-09T00:45:40.149646+00:00 [stderr] + exit 0
2025-07-09T00:45:40.149666+00:00 [exec  ] Termination: 0

State
-----

{
  "id": "web-server-1",
  "config": {
    "interval": "3s",
    "timeout": "10s",
    "command": "test.sh"
  },
  "status": {
    "status": "green",
    "code": 0,
    "description": "Success",
    "css": {
      "metadata": {
        "color": "#d0e6a5"
      }
    },
    "metadata": {
      "rps": "RPS: 443"
    },
    "log": [
      "2025-07-09T00:45:40.144844+00:00 [exec  ] Starting",
      "..."
      "2025-07-09T00:45:40.149666+00:00 [exec  ] Termination: 0"
    ]
  },
  "children": {}
}

CSS
---

/* web-server-1 */

/* Default rules */
[data-monitor-id="web-server-1"] {
  --monitor-id: "web-server-1";
  --monitor-status: green;
  --monitor-code: 0;
  --monitor-description: "Success";
  --monitor-metadata-rps: RPS: 443;
}
#web-server-1,
[data-monitor-id="web-server-1"] {
background-color: #d0e6a5 !important;
fill: #d0e6a5 !important;
}

#web-server-1 td:nth-child(2)::after {
content: "status=green retval=0"
}

#web-server-1 td:nth-child(3)::after {
content: "Success"
}

stylus run

Run Stylus (default command)

Usage

stylus run [OPTIONS] [FILE]

Arguments

  • [FILE] - The configuration file (or directory)

Options

  • --dry-run - Dry run the configuration (everything except running the server)
  • -v, --verbose... - Pass multiple times to increase the level of verbosity (overwritten by STYLUS_LOG)
  • -h, --help - Print help

The stylus run command starts the Stylus server and begins monitoring your infrastructure. This is the main command you'll use to run Stylus in production.

The command loads your configuration, starts the HTTP server, and begins running your monitor tests on their schedules. It also serves your status page and provides API endpoints for the current monitor states.

Examples

# Run using a directory (will look for config.yaml inside)
stylus run ~/my-stylus/

# Run with verbose output
stylus run -v ~/my-stylus/

# Dry run - test configuration without starting server
stylus run --dry-run ~/my-stylus/

If you specify a directory instead of a file, Stylus will look for config.yaml inside that directory. This is the most common way to run it.

Dry Run Mode

The --dry-run option tests your configuration without starting the server. This is useful for validating configuration syntax locally or in a CI/CD pipeline.

Server Endpoints

Once running, the server provides several endpoints:

  • / - Main status page
  • /status.json - JSON API with current monitor states
  • /style.css - Dynamic CSS with current monitor states
  • /log/<monitor-id> - Log output for specific monitors

Stopping the Server

Use Ctrl+C to stop the server gracefully. Stylus will clean up any running monitor processes.

Creating a Stylus Project

You can create a new Stylus project by running the stylus init command. This creates a new directory with a default configuration and a monitor.d directory with a single monitor test.

$ stylus init ~/stylus
Initializing directory: "~/stylus"...
Done!

Run `stylus "~/stylus"` to start the server

$ tree ~/stylus/
├── README.md
├── config.d
│   └── isoflow.json
├── config.yaml
├── monitor.d
│   ├── my-flaky-monitor
│   │   ├── config.yaml
│   │   └── test.sh
│   ├── my-group-monitor
│   │   ├── config.yaml
│   │   └── test.sh
│   └── my-monitor
│       ├── config.yaml
│       └── test.sh
└── static
    ├── README.md
    └── iframe.html

Once you've created the project, you can start the server with the stylus run command.

stylus run ~/stylus

If you open your web browser to http://localhost:8000, you should see a very basic default page with a green status. You'll also find a link to the status JSON and style CSS endpoints, as well as the per-monitor log output:

By default, Stylus renders a basic summary page for all of your monitors. This lets you work on your monitors before creating any custom pages. When you open the default project, you'll see three sections:

  1. A table visualization showing all the monitors
  2. An Isoflow visualization, updating the diagram when the status changes
  3. The monitor page listing the raw monitors, and their status, along with a button to view the log output for each

Default page Default page

Creating Monitors

A monitor is a script that is run periodically to check the status of a system. Monitors are defined in the monitor.d directory in a Stylus project.

Monitors consist of a configuration file and a test script.

Using the project we created in the previous section, let's take a look at the monitor that was created for us. The configuration from the initial project is:

$ cat ~/stylus/monitor.d/monitor/config.yaml
test:
  interval: 30s
  timeout: 10s
  command: test.sh

$ cat ~/stylus/monitor.d/monitor/test.sh
#!/bin/sh
echo 'Write your test script here'

The interval and timeout fields are used to control how often the monitor is run and how long it is allowed to run for. The command field is the path to the test script.

The test script is a simple shell script that will be run by the monitor.

To help you develop and debug monitors, Stylus provides a stylus test command that runs the test script and shows you exactly what happens.

$ stylus test --monitor monitor ~/stylus/config.yaml
Monitor Log
-----------

<timestamp> [exec  ] Starting
<timestamp> [stdout] Write your test script here
<timestamp> [exec  ] Termination: 0

State
-----

{
  "id": "monitor",
  "config": {
    "interval": "30s",
    "timeout": "10s",
    "command": "test.sh"
  },
  "status": {
    "status": "green",
    "code": 0,
    "description": "Success",
    "css": {
      "metadata": {}
    },
    "metadata": {},
    "log": [
      // ...
    ]
  },
  "children": {}
}

CSS
---

/* monitor */

/* Default rules */
[data-monitor-id="monitor"] {
  --monitor-id: "monitor";
  --monitor-status: green;
  --monitor-code: 0;
  --monitor-description: "Success";
}

Modifying the Monitor

Let's say that we want to change the test script to check if the server can see the internet. We'll using ping 8.8.8.8 as a proxy test for the internet existing.

Let's update test.sh to:

#!/bin/sh
ping -c 1 8.8.8.8

Now let's run the test again:

$ stylus test --monitor monitor ~/stylus/config.yaml
Monitor Log
-----------

<timestamp> [exec  ] Starting
<timestamp> [stdout] PING 8.8.8.8 (8.8.8.8): 56 data bytes
<timestamp> [stdout] 64 bytes from 8.8.8.8: icmp_seq=0 ttl=111 time=20.496 ms
<timestamp> [stdout] 
<timestamp> [stdout] --- 8.8.8.8 ping statistics ---
<timestamp> [stdout] 1 packets transmitted, 1 packets received, 0.0% packet loss
<timestamp> [stdout] round-trip min/avg/max/stddev = 20.496/20.496/20.496/0.000 ms
<timestamp> [exec  ] Termination: 0

...

As expected, the monitor successfully pings the internet.

More Complex Monitors

Monitors can be as simple or complex as you need them to be. Here are a few examples to get you started.

For example, you can use curl to request a JSON endpoint from a web service, and ensure that it returns a healthy response:

#!/bin/sh
set -xeuf -o pipefail
# Check the health of a service running on the monitor
STATUS=$(curl --fail http://my-web-server:8080/health | jq --raw-output '.status')
if [ "$STATUS" != "OK" ]; then
  echo "Service unhealthy: $STATUS"
  exit 1
fi

If you want to monitor network devices, you can often use the SNMP protocol.

Stylus has a built-in SNMP monitor that can be used to monitor network devices, but in some cases you may want to write a custom script to monitor SNMP devices for more complex checks.

See the manual for your networking device for the appropriate OIDs to use, or reference one of the following resources:

#!/bin/sh
set -xeuf -o pipefail
# Print the SNMP OID for the system description
snmpwalk -v 2c -c public my-network-router 1.3.6.1.2.1.1.1.0
# Print the SNMP OID for the system uptime
snmpwalk -v 2c -c public my-network-router 1.3.6.1.2.1.1.3.0

For more information on complex monitors, see the examples.

Visualizations

The default Stylus webapp provides a few builtin visualizations to get you up and running quickly.

Overview

Visualizations are configured in your config.yaml file under the ui.visualizations section. Each visualization has a type, title, description, and type-specific configuration options.

Configuration

ui:
  visualizations:
    - title: "Service Status"
      description: "Overview of all monitored services"
      type: "table"
    - title: "Network Diagram"
      description: "Visual representation of network topology"
      type: "svg"
      url: "/network.svg"
    - title: "Infrastructure Dashboard"
      description: "Interactive infrastructure overview"
      type: "iframe"
      url: "/dashboard.html"
      inject: true

Visualization Types

Row Visualization

The row visualization allows you to split a visualization into multiple columns with configurable widths. Each column can contain any other visualization type.

Configuration:

- title: "My Rows"
  description: "Row description"
  type: "row"
  columns:
    - type: "table"
      width: 1
    - type: "stack"
      width: 2
      stacks:
        - title: "Rack 1"
          rows:
            - id: "router"
              size: "small"
              layout: 3x1 4x2
    - type: "svg"
      width: 1
      url: "/network.svg"

The width parameter for each column determines how much of the available space the column should take up.

Table Visualization

The table visualization displays monitor status in a structured table format with status indicators and clickable rows for log viewing.

Configuration:

- title: "Service Status"
  description: "Overview of all monitored services"
  type: "table"

Stack Visualization

The stack visualization displays monitor status like a rack of servers.

Configuration:

- title: "Rack Status"
  description: "Rack of servers with status indicators"
  type: "stack"
  stacks:
    - title: "Rack 1"
      rows:
        - id: "group"
          size: "small"
          # Two 1x2 groups, two column-wise 2x2 groups (4 + 8 = 12 ports total)
          layout: 2x1x2 ~2x2x2
          # Order the status indicators by index as [6, 1, 2, 3, 4, 5, 10, 9, 8, 7, 20, 21]
          order: 6 1-5 10-7 20-21

The size parameter controls the size of the status indicators.

The layout parameter controls the layout of the status indicators. A single number indicates a 1-high group (ie: W), two numbers are WxH, three number is NxWxH, where N repeats the WxH group. A layout group prefixed with ~ indicates that the group is laid out column-wise rather than row-wise.

The order parameter controls the order of the status indicators. It is a space-separated list of ranges and single numbers. Ranges are inclusive, and support reverse order.

Each group is laid out in order from left to right on each row, unless the group is prefixed with ~, in which case it is laid out top-to-bottom on each column. Each port is taken from the next item in the order list.

SVG Visualization

The SVG visualization loads an SVG file and applies dynamic styling based on monitor status. This works well with network diagrams, flowcharts, and other vector graphics.

The SVG is loaded from the static/ directory, and is automatically updated when the status changes.

Configuration:

- title: "Network Diagram"
  description: "Visual representation of network topology"
  type: "svg"
  url: "/network.svg"

The SVG visualization automatically applies your configured CSS rules to the SVG content. The recommended method is setting data-monitor-id attributes on the SVG elements, and applying fill: CSS rules.

See the CSS Configuration section for more details.

Iframe Visualization

The iframe visualization embeds external HTML content with optional style injection, allowing you to create custom visualizations that fit into the existing pages. See Custom Monitor Pages for more details.

Configuration:

- title: "Infrastructure Dashboard"
  description: "Interactive infrastructure overview"
  type: "iframe"
  url: "/dashboard.html"
  inject: true

When inject: true is set, the monitor CSS is automatically injected into the iframe, applying to the content within.

Isoflow Visualization

The Isoflow visualization provides interactive diagrams with dynamic data updates.

Configuration:

- title: "Service Flow"
  description: "Interactive service dependency diagram"
  type: "isoflow"
  config: "service-flow"

Isoflow visualizations require initial data to be placed in config.d/{config-name}.json. The data is automatically updated with status information when available.

Fullscreen Mode

All visualizations support fullscreen mode for detailed viewing. Click the fullscreen button () in the top-right corner of any visualization card.

Examples

Simple Status Dashboard

A table and SVG diagram.

ui:
  visualizations:
    - title: "Service Status"
      description: "Overview of all monitored services"
      type: "table"
    - title: "Network Topology"
      description: "Network diagram with status colors"
      type: "svg"
      url: "/network.svg"

A table, iframe, and Isoflow diagram.

ui:
  visualizations:
    - title: "Service Status"
      description: "Quick overview of all services"
      type: "table"
    - title: "Infrastructure Diagram"
      description: "D3.js infrastructure visualization"
      type: "iframe"
      url: "/infrastructure.html"
      inject: true
    - title: "Service Dependencies"
      description: "Interactive dependency flow"
      type: "isoflow"
      config: "dependencies"

A row visualization with multiple columns.

ui:
  visualizations:
    - title: "Dashboard"
      description: "Multi-column dashboard layout"
      type: "row"
      columns:
        - type: "table"
          width: 1
        - type: "stack"
          width: 2
          stacks:
            - title: "Rack 1"
              rows:
                - id: "router"
                  size: "small"
                  layout: 3x1 4x2
        - type: "svg"
          width: 1
          url: "/network.svg"

Advanced Usage

Custom Visualization Development

For complex visualizations, you can create custom HTML/JavaScript applications and embed them using the iframe visualization type. This allows for complex interactive dashboards, real-time data visualization, and integration with external monitoring systems.

See the Custom Monitor Pages section for more details.

Creating Custom Monitor Pages

Stylus ships with a built-in webapp that includes a number of visualizations, but you can also use it to serve any content you like, even fully replacing the built-in webapp.

Rendering Technologies

Stylus works particularly well with SVG diagrams, but can use any markup language that supports either CSS or dynamic updates via JSON data (eg: Angular, React, etc).

HTML

React is a popular library for building user interfaces. Using /status.json as the data source, you can create a simple page with the current state of the monitors.

The IsoFlow component for React allows you to load a model and render it as an isometric diagram with a little bit of glue in Stylus:

IsoFlow

SVG

SVG is a flexible image format that conveniently supports CSS styling. See the SVG tutorial for more details on building a diagram using diagrams.net.

d3.js is a popular library for creating interactive diagrams. See the d3.js example in the repository for an example of how to use it with Stylus.

Hooks and Endpoints

There are number of provided hooks for updating status, and depending on which technology you'd like to use for your status page, you can choose which one makes sense.

Monitors are independent of the the technology you use to render your page, so you can start with a very basic page and then add more complexity as you go.

CSS

The simplest way to create a monitoring page is to use CSS to update the status of the page.

The /style.css route serves live CSS with the current state of the monitors.

For each monitor, Stylus will generate a CSS block with the current state of the monitor as CSS variables (including any metadata your script has generated):

[data-monitor-id="web-server-1"] {
  --monitor-id: "web-server-1";
  --monitor-status: green;
  --monitor-code: 0;
  --monitor-description: "Success";
  --monitor-metadata-rps: RPS: 702;
}

These CSS variables can be used for basic styling of the page, but it is highly recommended to generate more complex CSS rules to style the page.

In your project's config.yaml file, you can specify a number of rules to style the page. For each rules in this section, a CSS block will be generated for each monitor.

For example, adding these rules:

  # Specify a number of rules - selector/declaration pairs. Each pair will generate a CSS block.
  rules:
    # Style the HTML/SVG with the appropriate status color
    - selectors: |
        #{{monitor.id}},
        [data-monitor-id="{{monitor.id}}"] > *
      declarations: |
        background-color: {{monitor.status.css.metadata.color}} !important;
        fill: {{monitor.status.css.metadata.color}} !important;

... will generate the following CSS block for each monitor:

/* web-server-1 */

/* Default rules */
[data-monitor-id="web-server-1"] {
  --monitor-id: "web-server-1";
  --monitor-status: green;
  --monitor-code: 0;
  --monitor-description: "Success";
  --monitor-metadata-rps: RPS: 702;
}
#web-server-1, [data-monitor-id="web-server-1"] {
    background-color: #d0e6a5 !important;
    fill: #d0e6a5 !important;
}

See the CSS Configuration section for more details.

JSON

The /status.json route serves the current state of the monitors as JSON.

This can be used for dynamic rendering of the page, or for updating the page with a script.

Each monitor's latest status is available in the JSON response, along with its log output, and any metadata generated by the monitor script.

{
    "id": "database",
    "config": {
        "interval": "5s",
        "timeout": "15s",
        "command": "..."
    },
    "status": {
        "status": "green",
        "code": 0,
        "description": "Success",
        "css": {
            "metadata": {
                "color": "#d0e6a5"
            }
        },
        "metadata": {},
        "log": [
            "2025-07-08T23:51:05.732946+00:00 [exec  ] Starting",
            "..."
        ]
    },
    "children": {}
}

The /config.json route serves the global configuration for the project.

{
    "version": 1,
    "server": {
        "port": 8000,
        "listen_addr": "0.0.0.0"
    },
    "monitor": {
        "dir": "monitor.d"
    }
}

Server Configuration

The server configuration is stored in config.yaml in the root directory of the configuration. This controls the overall server behaviour (including listening ports) and tells Stylus where to find your monitors (monitor.d by default).

# Stylus will fail to load any configuration without a version of 1 (for future extensibility)
version: 1

# HTTP server configuration
server:
  # Listen port (default: 80, but init command uses 8000 for development)
  port: 8000
  # Static file directory
  static: static

# Monitor configuration
monitor:
  # The top-level directory that Stylus looks for monitor directories
  dir: monitor.d

css:
  # Arbitrary metadata can be associated with each of the six states: blank (no state),
  # red (failed), yellow (timed out), green (success), blue (highlight), or orange (warning).

  # Use metadata to get prettier colors - note that we can add arbitrary string keys and values here
  metadata:
    blank:
      color: "white"
    red:
      color: "#fa897b"
    yellow:
      color: "#ffdd94"
    green:
      color: "#d0e6a5"
    blue:
      color: "#3b82f6"
    orange:
      color: "#f9b356"

  # Specify a number of rules - selector/declaration pairs. Each pair will generate a CSS block.
  rules:
    # Style the HTML/SVG with the appropriate status color
    - selectors: "
        #{{monitor.id}},
        [data-monitor-id=\"{{monitor.id}}\"] > *
      "
      declarations: "
        background-color: {{monitor.status.css.metadata.color}} !important;
        fill: {{monitor.status.css.metadata.color}} !important;
      "
    # Add some text for the status/return value of the script
    - selectors: "
        #{{monitor.id}} td:nth-child(2)::after
      "
      declarations: "
        content: \"status={{monitor.status.status}} retval={{monitor.status.code}}\"
      "

CSS Configuration

The CSS configuration controls how Stylus generates dynamic stylesheets based on monitor status. This includes metadata for different states and CSS rules for styling your HTML/SVG elements.

# config.yaml
# version: 1
# server: ...
# monitor: ...

css:
  # Arbitrary metadata can be associated with each of the six states: blank (no state),
  # red (failed), yellow (timed out), green (success), blue (highlight), or orange (warning).

  # Use metadata to get prettier colors - note that we can add arbitrary string keys and values here
  metadata:
    blank:
      color: "white"
    red:
      color: "#fa897b"
    yellow:
      color: "#ffdd94"
    green:
      color: "#d0e6a5"
    blue:
      color: "#3b82f6"
    orange:
      color: "#f9b356"

  # Specify a number of rules - selector/declaration pairs. Each pair will generate a CSS block.
  rules:
    # Style the HTML/SVG with the appropriate status color
    - selectors: |
        #{{monitor.id}},
        [data-monitor-id="{{monitor.id}}"] > *
      declarations: |
        background-color: {{monitor.status.css.metadata.color}} !important;
        fill: {{monitor.status.css.metadata.color}} !important;
    # Add some text for the status/return value of the script
    - selectors: |
        #{{monitor.id}} td:nth-child(2)::after
      declarations: |
        content: "status={{monitor.status.status}} retval={{monitor.status.code}}"

CSS Interpolation

Interpolation is used in the css block to control the display. The interpolation library under the hood is handlebars-rust and any of the advanced syntaxes may be used.

Generally a monitor's output is interpolated from its status JSON, which will have a following form like the given example below:

{
  "id": "my-id",
  "config": {
    "interval": "1m",
    "timeout": "30s",
    "command": "/full/path/to/test.sh"
  },
  "status": {
    "status": "green",
    "code": 0,
    "description": "Success",
    "css": {
      "metadata": {
        "color": "#d0e6a5"
      }
    },
    "metadata": {
      "key": "value1"
    }
  }
}

The root object is named monitor, and you may choose to use any of the keys as such:

{{monitor.id}} = my-id
{{monitor.status.status}} = green
{{monitor.status.css.metadata.color}} = #d0e6a5
{{monitor.status.metadata.key}} = value1

You may use additional text content around the interpolation blocks. For example, background-color: {{monitor.status.css.metadata.color}} !important; will interpolate to background-color: #d0e6a5 !important.

Monitor Configuration

Monitor configurations define how Stylus tests your infrastructure components. Each monitor consists of a test script that runs on a schedule and reports the status back to Stylus.

Monitor Types

Stylus supports several types of monitors:

Logging

Output from the test's standard output and standard error streams are captured and available from the logging endpoint.

Monitor States

The state of a monitor is determined by the return value of the test script or manually set by scripts/expressions. The six states, in order of precedence, are:

StateDescriptionHow it's set
Red🔴Tests that fail by returning a value other than zeroAutomatic (exit code ≠ 0)
Orange🟠Warning stateManual (scripts/expressions)
Yellow🟡A test that has timed outAutomatic (timeout)
Blue🔵Highlight stateManual (scripts/expressions)
Green🟢Tests that return zero (success)Automatic (exit code = 0)
BlankA test that has not run or completed yetAutomatic (initial state)

Metadata

Tests scripts may also set metadata associated with the run. More information on this is available in Advanced Configuration.

Testing Your Configurations

Since monitor scripts with metadata can be tricky to get right, Stylus includes a stylus test command that lets you develop your test script interactively. The output shows your script's stdout and stderr, plus the parsed monitor state as JSON, and the final rendered CSS.

Standard Monitor

A standard monitor consists of a single test for a single host. This is the most common type of monitor in Stylus.

Configuration

test:
  # (optional) The internal ID to use for this test. If omitted, the ID is inferred from the monitor directory's name.
  id: foo
  # How often the test is run. The interval restarts from the last success or failure of the test.
  interval: 60s
  # How long the script will be given to run before it is killed.
  timeout: 30s
  # The test command to run, relative to the monitor directory. The PATH is not used and the file must be
  # directly executable.
  command: test.sh

Example

Here's a simple example of a standard monitor that pings a host:

test:
  id: web-server-ping
  interval: 30s
  timeout: 10s
  command: ping.sh

With a corresponding ping.sh script:

#!/bin/bash
ping -c 1 -W 5 example.com > /dev/null 2>&1
exit $?

Environment Variables

Stylus invokes all test scripts with a special environment variable named STYLUS_MONITOR_ID. This may be used as a convenient way to test multiple monitors using shared scripts. For example, a test script may be configured like so:

ssh $STYLUS_MONITOR_ID my-test-command

Group Monitor

A group monitor allows a single test script's execution to update the state for multiple entities. For example, you may be able to scrape the state of multiple hosts from a single controller, or you may want to monitor the state of multiple ports on a single switch.

Configuration

group:
    # The ID pattern for this group. This ID must use interpolation from axis values to generate a set of
    # globally unique IDs. 
    id: port-{{ index }}

    # The configuration axes.
    axes:
        # The Axis name and a list of values
        - name: index
          values: [0, 1, 2, 3, 4, 5, 6, 7]

    # A standard monitor configuration (see the Standard Monitor description)
    test:
        interval: 60s
        timeout: 30s
        command: test.sh

State Output

The group's test script is unique in that it must output state-modifying commands to its standard output. Each of these state-modifying commands starts with the prefix @@STYLUS@@.

echo '@@STYLUS@@ group.port-0.status.status="yellow"'
echo '@@STYLUS@@ group.port-1.status.status="green"'
echo '@@STYLUS@@ group.port-2.status.status="yellow"'
echo '@@STYLUS@@ group.port-3.status.status="green"'
echo '@@STYLUS@@ group.port-4.status.status="green"'
echo '@@STYLUS@@ group.port-5.status.status="yellow"'
echo '@@STYLUS@@ group.port-6.status.status="yellow"'
echo '@@STYLUS@@ group.port-7.status.status="red"'

Example

Here's a complete example of a group monitor that checks the status of multiple network ports:

group:
    id: port-{{ index }}
    axes:
        - name: index
          values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
    test:
        interval: 10s
        timeout: 30s
        command: test.sh

With a corresponding test.sh script:

#!/bin/bash
set -xeuf -o pipefail
echo '@@STYLUS@@ group.port-0.status.status="yellow"'
echo '@@STYLUS@@ group.port-1.status.status="green"'
echo '@@STYLUS@@ group.port-2.status.status="yellow"'
echo '@@STYLUS@@ group.port-3.status.status="green"'
echo '@@STYLUS@@ group.port-4.status.status="green"'
echo '@@STYLUS@@ group.port-5.status.status="yellow"'
echo '@@STYLUS@@ group.port-6.status.status="yellow"'
if [ $((RANDOM % 2)) -eq 0 ]; then
    echo '@@STYLUS@@ group.port-7.status.status="red"'
else
    echo '@@STYLUS@@ group.port-7.status.status="green"'
fi

Metadata

Group monitors can also set metadata for each individual monitor using the same @@STYLUS@@ prefix:

echo '@@STYLUS@@ group.port-0.status.description="Port 0 is experiencing high latency"'
echo '@@STYLUS@@ group.port-0.status.metadata.latency="150ms"'
echo '@@STYLUS@@ group.port-1.status.description="Port 1 is healthy"'
echo '@@STYLUS@@ group.port-1.status.metadata.latency="5ms"'

SNMP Monitor

If you want to monitor network devices, you can often use SNMP to extract information from the device. SNMP allows you to query the device for information about its status using OIDs (Object Identifiers), which are roughly standardized across different devices.

This is particularly useful for monitoring switches, routers, and other network infrastructure.

See the manual for your networking device for the appropriate OIDs to use, or reference one of the following resources:

The SNMP monitor works like a group monitor, but it uses SNMP to query the network device and create children for each detected interface.

Requirements

The SNMP monitor requires the snmpwalk or snmpbulkwalk command to be installed on the system. These are typically available in the net-snmp package.

Configuration

Interfaces are first filtered by the include and exclude conditions. If include is true and exclude is false, the interface is included in the monitor.

The red, orange, yellow, blue and green conditions are evaluated using the expressions language. The first condition that is true will determine the status of the interface.

By default, the SNMP monitor will show interfaces as green if they have ifOperStatus and ifAdminStatus set to "up", and blank if either of those is not "up".

snmp:
  # The ID pattern for this monitor. Uses interpolation from interface index.
  id: router-{{ index }}

  # How often the SNMP queries are performed
  interval: 60s

  # How long to wait for SNMP responses
  timeout: 30s

  # (optional) Filter to include certain interfaces (default: "true")
  include: |
    ifType == 'ethernetCsmacd'

  # (optional) Filter to exclude certain interfaces (default: "false")
  exclude: |
    contains(ifDescr, 'Loopback')

  # (optional) Condition that determines when the monitor should be red/error (default: "false")
  red: |
    ifOperStatus == "up" and ifSpeed < 1000000000

  # (optional) Condition that determines when the monitor should be orange/warning (default: "false")
  orange: |
    ifOperStatus == "up" and ifSpeed < 1000000000

  # (optional) Condition that determines when the monitor should be yellow/timeout (default: "false")
  yellow: |
    false

  # (optional) Condition that determines when the monitor should be blue/highlight (default: "false")
  blue: |
    ifOperStatus == "up" and ifSpeed > 1000000000

  # (optional) Condition that determines when the monitor should be green (default: "ifOperStatus == 'up' and ifAdminStatus == 'up'")
  green: |
    ifOperStatus == "up" and ifAdminStatus == "up"

  # SNMP target configuration
  target:
    host: 192.168.1.254
    port: 161 # optional, defaults to 161
    version: 2 # optional, defaults to 2 (1, 2, or 3)
    community: public # for SNMP v1/v2c
    # For SNMP v3:
    # username: myuser
    # auth_protocol: SHA  # optional
    # auth_password: myauthpass  # optional
    # privacy_protocol: AES  # optional
    # privacy_password: myprivpass  # optional
    bulk: true # optional, defaults to true

Parameters

Required Parameters

ParameterDescription
idThe monitor ID pattern using interpolation with {{ index }}
intervalHow often to perform SNMP queries
timeoutSNMP query timeout
target.hostThe IP address or hostname of the SNMP device

Optional Parameters

ParameterDescriptionDefault
includeA filter expression to include certain SNMP interfaces"true"
excludeA filter expression to exclude certain SNMP interfaces"false"
redA condition that determines when the monitor should show red status"false"
greenA condition that determines when the monitor should show green status"ifOperStatus == 'up' and ifAdminStatus == 'up'"
target.portSNMP port161
target.versionSNMP version (1, 2, or 3)2
target.communitySNMP community string (for v1/v2c)"public"
target.usernameSNMP username (for v3)-
target.auth_protocolAuthentication protocol (SHA, MD5, etc.)-
target.auth_passwordAuthentication password-
target.privacy_protocolPrivacy protocol (AES, DES, etc.)-
target.privacy_passwordPrivacy password-
target.bulkUse bulk SNMP operationstrue

Example

Here's a complete example of an SNMP monitor for a network switch:

snmp:
  id: switch-{{ index }}
  interval: 60s
  timeout: 30s
  exclude: |
    ifType != 'ethernetCsmacd'
  red: |
    ifOperStatus == "up" and ifSpeed < 1000000000
  target:
    host: 192.168.1.254
    community: public

This monitor will:

  • Query the switch at 192.168.1.254 every 60 seconds
  • Only monitor Ethernet interfaces (exclude other interface types)
  • Show red status if an interface is up but running at less than 1Gbps
  • Use the "public" community string for SNMP authentication

SNMP OIDs

The SNMP monitor automatically queries the SMTP ifTable table, and makes the OIDs available in expressions. The most useful ones you might want to use are:

FieldOID/VariableDescription
StatusifOperStatusOperational status of interface (up or down, ie: copper connected, fiber connected, etc.)
Admin StatusifAdminStatusAdministrative status of interface (up or down, ie: enabled or disabled by the administrator)
TypeifTypeType of interface (ethernetCsmacd, loopback, other, etc.)
SpeedifSpeedSpeed of interface
DescriptionifDescrDescription of interface
NameifNameName of interface
AliasifAliasAlias of interface
MTUifMtuMaximum Transmission Unit
Physical AddressifPhysAddressPhysical (MAC) address

You can see the OIDs available in the ifTable table on your specific device with the snmptable command:

# Print the headers of the ifTable table
snmptable -Ch -v 2c -c public 192.168.1.1 ifTable | head -1

SNMP Versions

The SNMP monitor supports SNMP v1, v2c, and v3. The default is to use v2c with the public community string. Bulk operations are enabled by default, but can be disabled for older devices.

SNMP v1/v2c

snmp:
  # ... other configuration ...
  target:
    host: 192.168.1.254
    version: 2 # or 1
    community: public

SNMP v3

snmp:
  # ... other configuration ...
  target:
    host: 192.168.1.254
    version: 3
    username: myuser
    auth_protocol: SHA
    auth_password: myauthpass
    privacy_protocol: AES
    privacy_password: myprivpass

Ping Monitor

The ping monitor uses the system ping command to check network connectivity to a host. It measures round-trip time and packet loss, making it useful for monitoring network latency and availability.

Configuration

The ping monitor evaluates conditions using the expressions language.

By default, the ping monitor will show:

  • Green if there's no packet loss and the round-trip time is within the warning timeout
  • Orange if there's no packet loss but the round-trip time exceeds the warning timeout, or if there was partial packet loss
  • Yellow if the ping command timed out
  • Red if there was complete packet loss
ping:
  # The host to ping (IP address or hostname)
  host: 8.8.8.8
  
  # How often to perform the ping test
  interval: 60s
  
  # How long to wait for ping responses before timing out
  timeout: 5s
  
  # (optional) Warning threshold for round-trip time (default: 1s)
  warning_timeout: 1s
  
  # (optional) Number of ping packets to send (default: 1)
  count: 1

  # (optional) Condition that determines when the monitor should be red/error (default: "lost == count")
  red: |
    lost == count
  
  # (optional) Condition that determines when the monitor should be orange/warning (default: "lost > 0 or (lost == 0 and rtt_max > warning_timeout)")
  orange: |
    lost > 0 or (lost == 0 and rtt_max > warning_timeout)
  
  # (optional) Condition that determines when the monitor should be green (default: "lost == 0")
  green: |
    lost == 0
  
  # (optional) Condition that determines when the monitor should be blue/highlight (default: "false")
  blue: |
    false
  
  # (optional) Condition that determines when the monitor should be yellow/timeout (default: "false")
  yellow: |
    false

Parameters

Required Parameters

ParameterDescription
hostThe IP address or hostname to ping
intervalHow often to perform ping tests
timeoutHow long to wait for ping responses

Optional Parameters

ParameterDescriptionDefault
warning_timeoutRound-trip time threshold for orange status1s
countNumber of ping packets to send1
redCondition for red status"lost == count"
orangeCondition for orange status"lost > 0 or (lost == 0 and rtt_max > warning_timeout)"
greenCondition for green status"lost == 0"
blueCondition for blue status"false"
yellowCondition for yellow status"false"

Expression variables

VariableDescription
countNumber of ping packets sent
lostNumber of packets lost
rtt_avgAverage round-trip time in microseconds
rtt_minMinimum round-trip time in microseconds
rtt_maxMaximum round-trip time in microseconds
warning_timeoutThe configured warning timeout value in microseconds

Example

Ping Google's DNS server using the default settings:

ping:
  host: 8.8.8.8

Ping Google's DNS server with custom settings:

ping:
  host: 8.8.8.8
  interval: 30s
  timeout: 10s
  warning_timeout: 500ms
  count: 3
  red: |
    lost > 0
  orange: |
    lost == 0 and rtt_min > warning_timeout

This monitor will:

  • Ping Google's DNS server (8.8.8.8) every 30 seconds with three packets
  • Wait up to 10 seconds for responses
  • Show orange/warning status if the shortest round-trip time exceeds 500ms (ie: all pings were slow)
  • Show red/error status if any packets are lost

Requirements

The ping monitor requires the ping command to be available on the system. This is typically installed by default on most Unix-like systems.

Expression Language

Stylus uses a custom expression language for evaluating conditions in monitor configurations. This language supports arithmetic operations, logical operations, string manipulation, and comparisons.

Overview

The expression language is currently only used in the SNMP and ping monitors.

Data Types

The expression language supports two main data types:

  • Integers: Whole numbers (e.g., 42, -17, 0)
  • Strings: Text values enclosed in quotes (e.g., "hello", 'world')

Literals

Numbers

42        // Positive integer
-17       // Negative integer
0         // Zero

Strings

Strings can be enclosed in single or double quotes:

"hello world"    // Double quotes
'hello world'    // Single quotes

Escape Sequences

Strings support escape sequences:

"hello \"world\""    // Escaped double quote
'hello \'world\''    // Escaped single quote
"hello \\world\\"    // Escaped backslash

Boolean Values

true      // Boolean true (evaluates to 1)
false     // Boolean false (evaluates to 0)

Arithmetic Operations

Basic Arithmetic

a + b     // Addition (numbers) or concatenation (strings)
a - b     // Subtraction
a * b     // Multiplication
a / b     // Division
a ^ b     // Exponentiation (a raised to power b)
-a        // Negation

Examples

2 + 3           // 5
"hello" + " " + "world"    // "hello world"
10 - 3          // 7
4 * 5           // 20
15 / 3          // 5
2 ^ 3           // 8
-5              // -5

Comparison Operations

Comparison Operators

a == b    // Equal to
a != b    // Not equal to
a > b     // Greater than
a < b     // Less than
a >= b    // Greater than or equal to
a <= b    // Less than or equal to

Examples

5 == 5           // true
"up" == "down"   // false
10 > 5           // true
"abc" < "def"    // true
7 >= 7           // true
3 <= 10          // true

Logical Operations

Logical Operators

a and b   // Logical AND
a or b    // Logical OR
not a     // Logical NOT

Examples

true and true    // true
true and false   // false
true or false    // true
false or false   // false
not true         // false
not false        // true

String Functions

String Manipulation

startswith(str, prefix)    // Check if string starts with prefix
endswith(str, suffix)      // Check if string ends with suffix
contains(str, substr)      // Check if string contains substring
length(str)                // Get string length

Examples

startswith("hello world", "hello")    // true
endswith("hello world", "world")      // true
contains("hello world", "lo wo")      // true
length("hello")                       // 5

Type Conversion Functions

Type Conversion

str(value)    // Convert value to string
int(value)    // Convert value to integer

Examples

str(42)       // "42"
int("123")    // 123
str(true)     // "1"
int("abc")    // 0 (default for failed conversion)

Precedence and Associativity

The expression language follows Python-like precedence rules (from lowest to highest):

  1. or
  2. and
  3. not
  4. Comparisons (==, !=, >, <, >=, <=)
  5. Addition/Subtraction (+, -)
  6. Multiplication/Division (*, /)
  7. Exponentiation (^)
  8. Functions, parentheses, literals, variables

Examples

a == 1 and b == 2 or c == 0    // ((a == 1) and (b == 2)) or (c == 0)
not a == 1                      // not (a == 1)
2 + 3 * 4                       // 2 + (3 * 4) = 14
2 ^ 3 + 1                       // (2 ^ 3) + 1 = 9

Truthiness

Values are considered "truthy" or "falsy" in logical operations:

  • Falsy values: 0, "" (empty string), false
  • Truthy values: Any non-zero number, any non-empty string, true

Examples

0 and "hello"      // false (0 is falsy)
1 and "hello"      // true (both are truthy)
"" or 42           // true (42 is truthy)
not 0              // true
not "hello"        // false

Context Variables

Depending on the context, the expression language has access to different local variables.

SNMP Examples

The SNMP monitor makes the OIDs for each interface available as context variables.

// Check if interface is up and admin enabled
ifOperStatus == "up" and ifAdminStatus == "up"

// Check if interface is Ethernet and not loopback
ifType == "ethernetCsmacd" and not contains(ifDescr, "Loopback")

// Check if interface speed is less than 1Gbps
ifSpeed < 1000000000

// Check if interface description contains specific text
contains(ifDescr, "10G Ethernet Adapter")

Advanced Configuration

Metadata

A test script may update metadata for the monitor, including the built-in status and description fields. These commands start with the prefix @@STYLUS@@ and may be output to standard output or standard error.

An example of metadata update commands is shown below:

echo '@@STYLUS@@ status.description="Custom (yellow)"'
echo '@@STYLUS@@ status.status="yellow"'
echo '@@STYLUS@@ status.metadata.key="value1"'

These may be referenced via standard interpolation, such as {{monitor.status.metadata.key}}.

Environment variables

Stylus invokes all test scripts with a special environment variable named STYLUS_MONITOR_ID. This may be used as a convenient way to test multiple monitors using shared scripts. For example, a test script may be configured like so:

ssh $STYLUS_MONITOR_ID my-test-command

General Tips

There are several approaches you can take to monitoring with Stylus. This section covers general best practices and tips for writing effective monitor scripts.

STYLUS_MONITOR_ID

The STYLUS_MONITOR_ID environment variable is set by Stylus to the monitor's ID when running a monitor script. This allows you to write monitor scripts that can be re-used across multiple monitors.

#!/bin/bash
set -xeuf -o pipefail
# Check the health of a service running on the monitor
curl --fail http://$STYLUS_MONITOR_ID:8080/health | jq --raw-output '.status'

Safe Scripting

Because monitor scripts may have a large number of moving parts, consider using safe shell scripting techniques to ensure that any failure of any kind will return an error code.

In addition set -x can be useful to print all commands that run as part of a monitor script. These are available in the logging endpoints and will show you the expansion of environment variables.

#!/bin/bash
set -xeuf -o pipefail

Script-relative Paths

The dirname command can be used to get the directory of the script that is running. This can be useful to locate configuration files or other resources that are needed by the script, which allows you to run the script from any directory outside of Stylus.

#!/bin/bash
set -xeuf -o pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

CONFIG_FILE=$DIR/config.yaml

Testing Your Configurations

As monitor scripts using metadata can be somewhat tricky to get right, Stylus includes a test command-line argument to allow you to develop your test script in a slightly more interactive manner.

The output from stylus test will include the test script's stdout and stderr streams, as well as the parsed monitor state as JSON, and the final rendered CSS.

Examples

See some example configurations and use cases for Stylus.

Monitors

Writing complex monitors can be challenging. Here are some examples of how to monitor various services and devices:

  • ping: Basic connectivity monitoring using ping
  • Web/HTML/API Scraping: Monitoring web services and APIs
  • SNMP: Monitoring network devices with SNMP
  • SSH: Remote monitoring over SSH

Projects

The github repository contains a number of example projects that you can use as a starting point for your own configuration.

  • Minimal: Basic configuration example
  • Metadata: Adding custom metadata to monitors
  • Simple Network: A basic network monitoring example
  • Group: Using group monitors to monitor multiple services with a single script
  • Dynamic: Using React for dynamic updates
  • D3.js: Using D3.js to create interactive diagrams
  • IsoFlow: Using IsoFlow to create isometric diagrams

Ping Monitoring

The simplest monitor is a ping script. One ping is usually enough for most cases. You can pass a timeout to ping, but Stylus will automatically kill processes if they run too long.

Built-in Ping Monitor

Stylus has a built-in ping monitor that can be used to monitor network connectivity.

ping:
  host: 8.8.8.8
  interval: 30s
  timeout: 10s
  count: 1

Custom Ping Script

You can also write a custom ping script to test the connectivity to a host.

test:
  id: my-ping-host
  interval: 30s
  timeout: 10s
  command: ping.sh

With a corresponding ping.sh script:

#!/bin/bash
# Pings the host with the same name as the monitor's ID
ping -c 1 ${STYLUS_MONITOR_ID}

Pinging a custom host:

#!/bin/bash
ping -c 1 8.8.8.8

Pinging with a custom per-packet timeout:

#!/bin/bash
# -W is in seconds on Linux
ping -c 1 -W 5 ${STYLUS_MONITOR_ID}

Alternatives

For more complex monitoring scenarios, consider using SSH, SNMP, or HTML/API scraping.

SSH Monitoring

SSH monitoring allows you to execute commands on remote systems and check their status. This is useful for monitoring servers, network devices, and other systems that support SSH access.

SSH Configuration

To make your life easier, you can collect all of your SSH credentials in a configuration file. The examples in this section will assume you've got a central SSH configuration file.

host pi-*
	User matt
host tower
	User root
host unifi-*
	User admin
host *
	IdentityFile /srv/ssh_id_rsa

Security Considerations

Depending on your security requirements, you may wish to loosen some of your SSH client's security check requirements. By disabling strict host key checking and host IP checking, your monitors will be more reliable but there will be some tradeoffs.

ssh <...> -oStrictHostKeyChecking=no -oCheckHostIP=no

Basic SSH Check

# Assumes that `ssh_config` lives in the same folder as this script 
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
SSH_CONFIG=$DIR/ssh_config

ssh_check () {
    local host="$STYLUS_MONITOR_ID"
    ssh -F $SSH_CONFIG $host -oStrictHostKeyChecking=no -oCheckHostIP=no "true"
}

ssh_check

SSH + Gather Basic Hardware Info

# Assumes that `ssh_config` lives in the same folder as this script 
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
SSH_CONFIG=$DIR/ssh_config

ssh_check () {
    local host="$STYLUS_MONITOR_ID"
    ssh -F $SSH_CONFIG $host -oStrictHostKeyChecking=no -oCheckHostIP=no \
        "uname -a && uptime && cat /proc/cpuinfo | grep -i -E '(hardware|model|stepping|revision)' | sort | uniq"
}

ssh_check

Alternatives

For simpler connectivity tests, consider ping monitoring. For network devices, consider SNMP monitoring.

SNMP Monitoring

SNMP (Simple Network Management Protocol) is a useful way to write more complex checks for network devices, but the output of the tools requires some massaging.

Stylus has a built-in SNMP monitor that can be used to monitor network devices, but in some cases you may want to write a custom script to monitor SNMP devices for more complex checks.

Basic SNMP Check

This performs a simple SNMP "ping" to the device, asking for its sysDescr and sysUpTime OIDs (system description and uptime respectively).

#!/bin/sh
set -xeuf -o pipefail
# Print the SNMP OID for the system description
snmpwalk -v 2c -c public my-network-router 1.3.6.1.2.1.1.1.0
# Print the SNMP OID for the system uptime
snmpwalk -v 2c -c public my-network-router 1.3.6.1.2.1.1.3.0

SNMP Group Monitor

You can use a group monitor to monitor multiple devices at once using SNMP.

snmp_check () {
    local host="$STYLUS_MONITOR_ID"
    ARR=`snmpbulkwalk -OsQ -c public $host ifTable`
    jq -n --arg inarr "${ARR}" '[$inarr | split("\n")
        | .[]
        | capture("(?<key>[^\\.]+)\\.(?<idx>\\d+)\\s+=\\s+(?<value>.*)")
    ] | group_by(.idx) | .[] | from_entries'
}

# Some legacy devices only respond to SNMP v1
snmp_v1_check () {
    local host="$STYLUS_MONITOR_ID"
    ARR=`snmpwalk -v1 -OsQ -c public $host ifTable`
    jq -n --arg inarr "${ARR}" '[$inarr | split("\n")
        | .[]
        | capture("(?<key>[^\\.]+)\\.(?<idx>\\d+)\\s+=\\s+(?<value>.*)")
    ] | group_by(.idx) | .[] | from_entries'
}

snmp_parse () {
    cat - | jq -r '
        # Only parse ethernet ports and omit anything that looks like a vlan port (ending with a .xxxx)
        select(.ifType=="ethernetCsmacd" and (.ifDescr | test("\\.\\d+$") | not)) 
        | "@@STYLUS@@ group.'$STYLUS_MONITOR_ID'-" 
            + .ifIndex 
            + ".status.status=" 
            + (if .ifOperStatus == "up" then "\"green\"" else "\"blank\"" end)' 
}

# Map the SNMP JSON output to @@STYLUS@@ metadata updates
snmp_check | snmp_parse

Alternatives

For simpler connectivity tests, consider ping monitoring. For server monitoring, consider SSH monitoring.

Web/HTML/API Scraping

Some devices can be tested using a simple cURL script, while others require more complex HTML parsing and API interaction.

Simple cURL Check

curl --silent --max-time 2 <url>

HTML Scraping with pup and jq

In other cases you may want to scrape HTML. The pup tool is included in the docker image to make this easier. You can use the json{} filter to pass a pre-processed HTML DOM tree to jq for further processing.

This example scrapes the power state from a Web Power Switch 7:

#!/bin/bash
set -euf -o pipefail

function fetch() {
    curl --silent --max-time 2 --basic -u <credentials> <url> \
        | pup -c "table table tr[bgcolor=#F4F4F4] json{}" \
        | jq "[.[] | [.children | .. | .text? | select(. != null)] | { \"name\": .[1], \"state\": (.[2]==\"ON\") }]"
}

n=0
until [ "$n" -ge 10 ]
do
   HTML=`fetch` && break
   n=$((n+1)) 
done

echo $HTML | \
    jq -r -e ". | to_entries | .[] | \"@@STYLUS@@ group.power-\" + (.key + 1 | tostring) + \".status.status=\" + if .value.state then \"\\\"green\\\"\" else \"\\\"blank\\\"\" end"

Alternatives

For simpler connectivity tests, consider ping monitoring. For server monitoring, consider SSH monitoring.

Creating SVG Diagrams

Short story: Using a tool like diagrams.net, create an SVG diagram of your network. Attach an SVG DOM attribute to the elements you'd like to style with status changes. If you're using diagrams.net, this can be done using the svgdata plugin. Alternatively, you can use the automatic identifiers generated by your SVG editor as your monitoring identifiers.

From the SVG you've generated, create CSS selectors and rules that will apply styles to the appropriate elements as statuses change. The SVG fill attribute is a good candidate to change, but ensure that you're using !important on all your rules to override the fill colors created by your SVG editor.

Detailed guide

Enable one of the networking diagram sets on diagrams.net that matches the style of diagram you'd like to create.

For each of the networking elements you've dropped on the canvas, click the "edit data" button to add metadata.

Set a custom property named infra-id. This will be the ID we'll later use to configure the diagram.

Once you've completed your diagram, export it as an SVG.

These are the recommended options you can use to create a standalone SVG file.

The final result.

And the final SVG source:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="181" height="167" viewBox="-0.5 -0.5 181 167">
<defs>
<filter id="dropShadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="1.7" result="blur"/>
<feOffset in="blur" dx="3" dy="3" result="offsetBlur"/>
<feFlood flood-color="#3D4574" flood-opacity="0.4" result="offsetColor"/>
<feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur"/>
<feBlend in="SourceGraphic" in2="offsetBlur"/>
</filter>
</defs>
<g filter="url(#dropShadow)">
<g id="cell-f1RpjmRWZniSFc_S4n2y-1" content="&lt;object label=&quot;&quot; infra-id=&quot;firewall&quot;/&gt;" data-label="" data-infra-id="firewall">
<rect x="0" y="20" width="50" height="44" fill="none" stroke="none" pointer-events="all"/><path d="M 0 64 L 0 54.01 L 23.64 54.01 L 23.64 64 Z M 25 54.01 L 50 54.01 L 50 64 L 25 64 Z M 37.5 52.75 L 37.5 42.71 L 50 42.71 L 50 52.75 Z M 36.04 42.71 L 36.04 52.75 L 13.91 52.75 L 13.91 42.71 Z M 12.55 52.75 L 0 52.75 L 0 42.66 L 12.55 42.66 Z M 23.59 41.44 L 0 41.44 L 0 31.4 L 23.59 31.4 Z M 25 31.4 L 50 31.4 L 50 41.44 L 25.1 41.44 Z M 37.5 30.09 L 37.5 20 L 50 20 L 50 30.09 Z M 36.04 20.05 L 36.04 30.09 L 13.91 30.09 L 13.91 20.05 Z M 12.45 30.09 L 0 30.09 L 0 20.05 L 12.45 20.05 Z" fill="#00188d" stroke="none" pointer-events="all"/></g><g id="cell-f1RpjmRWZniSFc_S4n2y-3" content="&lt;object label=&quot;&quot; infra-id=&quot;server-a&quot;/&gt;" data-label="" data-infra-id="server-a"><rect x="140" y="0" width="24" height="50" fill="none" stroke="none" pointer-events="all"/>
<path d="M 140 50 L 140 2.51 C 140 1.12 141.11 0 142.47 0 L 161.53 0 C 162.89 0 164 1.12 164 2.51 L 164 50 Z M 143.6 45.08 L 160.4 45.08 L 160.4 42.67 L 143.6 42.67 Z M 160.4 40.26 L 160.4 37.8 L 143.6 37.8 L 143.6 40.26 Z M 143.6 9.79 L 160.4 9.79 L 160.4 7.33 L 143.6 7.33 Z" fill="#00188d" stroke="none" pointer-events="all"/>
</g>
<g id="cell-f1RpjmRWZniSFc_S4n2y-4" content="&lt;object label=&quot;&quot; infra-id=&quot;server-c&quot;/&gt;" data-label="" data-infra-id="server-c">
<rect x="90" y="110" width="24" height="50" fill="none" stroke="none" pointer-events="all"/>
<path d="M 90 160 L 90 112.51 C 90 111.12 91.11 110 92.47 110 L 111.53 110 C 112.89 110 114 111.12 114 112.51 L 114 160 Z M 93.6 155.08 L 110.4 155.08 L 110.4 152.67 L 93.6 152.67 Z M 110.4 150.26 L 110.4 147.8 L 93.6 147.8 L 93.6 150.26 Z M 93.6 119.79 L 110.4 119.79 L 110.4 117.33 L 93.6 117.33 Z" fill="#00188d" stroke="none" pointer-events="all"/>
</g>
<g id="cell-f1RpjmRWZniSFc_S4n2y-5" content="&lt;object label=&quot;&quot; infra-id=&quot;server-b&quot;/&gt;" data-label="" data-infra-id="server-b">
<rect x="150" y="80" width="24" height="50" fill="none" stroke="none" pointer-events="all"/>
<path d="M 150 130 L 150 82.51 C 150 81.12 151.11 80 152.47 80 L 171.53 80 C 172.89 80 174 81.12 174 82.51 L 174 130 Z M 153.6 125.08 L 170.4 125.08 L 170.4 122.67 L 153.6 122.67 Z M 170.4 120.26 L 170.4 117.8 L 153.6 117.8 L 153.6 120.26 Z M 153.6 89.79 L 170.4 89.79 L 170.4 87.33 L 153.6 87.33 Z" fill="#00188d" stroke="none" pointer-events="all"/>
</g>
</g>
</svg>