A modern, cross-platform SNMP MIB browser, MIB editor and monitoring desktop application —
dashboards drawn from shareable presets, a trap receiver, and alerts routed to syslog, a webhook or email.
One native binary, built with Wails (Go + Svelte).
Website · Try it in your browser · Documentation · Download
- Try it without a device — a simulated server or switch on this machine, in two minutes
- Credential profiles — named SNMP identifiers, given to targets instead of the defaults
- Writing a dashboard preset — what to poll on one kind of equipment, and how to draw it
- Message templates — the subject and body an alert is sent with
- Adding MIBs — importing, enabling, diagnosing and editing MIB modules
- Simulated devices and custom simulator models — the simulator in full
- Configuration — the settings, and every file SnmpLens writes
- How SnmpLens compares — against iReasoning, ManageEngine and SnmpB, with sources
The same, with screenshots, is on the documentation site.
SNMP Operations — a walk of ifTable pivoted into columns and split by INDEX
The three desktop tools most people reach for before this one. Every cell about another product
is taken from that product's own page and dated, because a table like this stops being true
quietly — it is generated from tools/comparison.json so that this
copy and the one on the site cannot disagree.
| SnmpLens | iReasoning MIB Browser | ManageEngine MIB Browser | SnmpB | |
|---|---|---|---|---|
| Licence | MIT, open source | Proprietary | Proprietary | GPLv2, open source |
| Price | Free | Free / $495 / $895 | Free | Free |
| The free version may be used at work | Yes | No — private use only | Yes | Yes |
| Windows / macOS / Linux | All three | All three | Windows and Linux | All three, and BSD |
| SNMPv3 in the version you can use for free | Yes | No — Professional and above | Yes | Yes |
| MIB modules the free version loads | No limit | 10 | No stated limit | No stated limit |
| Trap receiver | Yes | Yes | Yes | Yes |
| MIB editor | Yes, with diagnostics | Not listed | Not listed | Yes |
| Monitoring that continues with the window closed | Yes | Enterprise | Not listed | Not listed |
| Alerts routed to syslog, a webhook or email | All three | Email (Pro) | Not listed | |
| Device simulator for testing without hardware | Built in, 14 models | Separate product | Not listed | Not listed |
| Source code available | Yes, MIT | No | No | Yes, GPLv2 |
| Latest release the project itself dates | v1.6.4, October 2026 | Not dated on the product page | Not dated on the product page | 0.9, June 2019 |
Every cell about another product is what that product's own documentation stated on 2026-10-13: ireasoning.com, manageengine.com, sourceforge.net. "Not listed" means their feature list does not mention it — a fact about their documentation, not a claim about their software. The long form, with what each of them is better at, is on the comparison page.
- GET / SET / GETNEXT / GETBULK / WALK with concurrent multi-target execution
- Conceptual tables — a walk is pivoted into columns and split by INDEX following RFC 2578 §7.7, so
tcpConnTablereads as four index fields instead of one opaque sub-OID, name-keyed tables show names, and rows sort numerically. Sortable, filterable, exportable to CSV - Row creation and deletion on tables that define a
RowStatuscolumn, written as one atomic SET so the agent never sees a half-built row - Smart Value Formatting — TimeTicks as a readable duration (e.g.
127d 3h 46m), enumerations asethernetCsmacd(6), large numbers with thousand separators - Result Filtering — regex-capable filter bar on WALK/GETBULK results to search across OIDs, names, types, and values
- One-click Copy — copy any OID, value, or target to clipboard with a single click
- Device Comparison — side-by-side multi-target comparison with delta and percentage differences
- Double-click GET — double-click any MIB tree node to instantly perform a GET operation
- SNMPv3 Full Support — authentication (MD5, SHA, SHA-256, SHA-512) and privacy (DES, AES, AES-256)
- IPv6 — targets, the trap listener and traceroute, including link-local addresses with a zone
- SNMP Debug — live packet inspection, with community strings and passphrases scrubbed from the log
- Presets — small JSON files describing what to poll on one kind of equipment and how to draw it. Meant to be shared: a preset picks from a fixed vocabulary of widgets and cannot carry anything that executes
- Preset library — Settings → Presets: add a preset by file or by dropping it on the window. A preset with problems is kept and listed with them, and cannot be bound until they are fixed
- Cost stated before binding — OIDs, cadence, requests and values a day, how many readings carry a threshold and how many of those can raise an incident
- Device detection — asks an equipment for its
sysObjectIDand puts the presets written for it first - Discovery at bind time — a widget can name a column to walk instead of listing its instances, so one preset fits a 24-port switch and a 48-port chassis, with every port named the way the equipment names it
- Layout — widgets placed on a twelve-column grid, collapsing to one column on a narrow window
- Thresholds carried by the preset — materialised into the monitoring session when it is bound, and editable afterwards like any other
- Six widget kinds — latest value, per-second rate, chart, state tile, grid of states (a switch's port panel), and a map
- Maps — boxes, lines, labels and dots bound to OIDs and coloured by what each reading is doing, drawn over a picture of your rack or site. Backgrounds are yours: PNG, JPEG or GIF, checked by decoding them; SVG is deliberately refused
- Several equipments on one dashboard — one monitoring session per equipment, drawn together as a group
- A snapshot, not a link — editing or deleting a preset never changes a dashboard already bound; binding again adopts the new version
- Six example presets — IF-MIB interfaces, switch ports, Cisco, a HOST-RESOURCES server, a UPS (RFC 1628), and a 24-port switch drawn as its front panel
- Simulated devices on this machine — SNMP agents on loopback addresses, answering v1, v2c and v3 (the whole USM) from fourteen models: Linux and Windows servers, an iDRAC, Catalyst switches, a router, a MikroTik, a FortiGate, a UniFi access point, a Synology NAS, UPSs, a rack PDU, a printer and an environment probe
- A whole agent's walk — IF-MIB, IP, TCP, UDP, ENTITY, BRIDGE, LLDP, HOST-RESOURCES, UCD and each vendor's own MIBs, agreeing with each other, with counters that move and wrap
- Notifications — traps and INFORMs in every version, to SnmpLens or anywhere on the network: at boot, on a refused request, on a schedule, or on demand
- Writable — a write community, or SNMPv3 users allowed to SET;
RowStatusrows created and destroyed as the loaded MIBs describe them - Faults — latency, loss, a device that goes silent,
genErrortooBig, and counters running up to 1000× faster - Shaped data — a model's size (ports, disks, outlets, processors), values of its own OID by OID, and a live preview of what it answers
- Your own models — a JSON file, a package carrying recorded walks, or a real device recorded from SnmpLens
- A bench as a file — devices exported with or without their passwords, and imported on another machine
- Hierarchical Tree navigation with collapsible nodes
- Global Search across name, OID, description, and syntax
- Filter Chips for dynamic filtering
- Favorites — bookmark frequently used OIDs
- Node Details panel with full OID metadata (syntax, access, status, units, parent, description)
- Custom MIBs — load your own MIB files from a persistent directory, and switch any module off
- Drag & Drop Import — drop MIB files or folders anywhere in the app. Recursive folder scanning and duplicate detection
- Why a MIB did not load — the stage it reached, the line and column with an excerpt, the imports that could not be satisfied and the symbols each was needed for, and a plain answer for the files people download by mistake (an HTML page, a PDF, a zip, UTF-16)
- Dependency tree — Settings → MIBs shows what imports what, which modules are missing, and which symbols they were needed for
- Syntax highlighting, find and replace (
Ctrl + F), undo and redo grouped by edit rather than by keystroke - Three kinds of checking — syntax errors with line and column as you type, whether the module loads, and a semantic pass on a pause that catches what loading does not: unknown types, duplicate OIDs, unresolved parents, missing modules in
FROM, undefinedINDEXobjects - Fix imports — works out which symbol comes from which module and edits the IMPORTS clause as text, so comments and alignment survive
- Outline — every definition in the file with its kind, syntax, access and status, filterable, and still there above a syntax error
- Safe to use on standard MIBs — bundled MIBs are backed up before they are overwritten, can be restored to the shipped version, and the tree is health-checked after a reload. Unsaved work survives closing the window
- Real-time OID Polling with interactive Chart.js graphs
- Multiple View Modes — raw values, delta, rate (per-second), and latency. Counter wraps are corrected, and rates derived from the time that actually elapsed
- Threshold Alerts — a minimum, a maximum and how long it must be breached for. A value oscillating around its threshold is one incident, not forty; a device that stops answering is its own kind of incident
- Runs in the background — the poll clock lives in the Go backend, so sessions, thresholds and alerts keep running with the window closed. Tray icon, start at login (per-user, never asks for elevation), and sessions resumed at startup
- Overload guardrail — when a round no longer fits inside its cadence, the session widens its own period and says so, instead of polling the device flat out. Keep my cadence overrides it
- Session Management — create, pause, resume, and delete monitoring sessions
- Historical Data — SQLite storage for long-term trending with time-range queries
- Event journal — traps, threshold episodes opened and resolved, devices that stopped answering, deliveries that failed. Filterable and exportable
- Routing rules — match on category, severity, source and OID prefix, with priorities, a "stop here" flag and quiet hours in your own timezone
- Destinations — syslog (UDP, TCP, or TLS per RFC 5425, with mutual TLS), webhook (bearer token, custom headers), and email (implicit TLS or STARTTLS). Each accepts a CA certificate, so an internal collector can be trusted without turning verification off
- Message templates — per destination, over a fixed list of variables. A webhook can send its own JSON — which is how you talk to Slack, Teams or Alertmanager — with a preview rendered by the same code that sends it
- Durable outbox — deliveries survive a closed window or an unreachable relay, with retries and backoff. A delivery log shows what was sent, what is waiting, and what was given up on, with a retry button
- Trap Listener — receive SNMPv1/v2c/v3 traps and acknowledged INFORMs on a configurable port
- Built for storms — an 8 MiB receive buffer; measured with no loss at 2000 traps per second sustained
- Trap Sender — send traps, or INFORMs to check a receiver acknowledges them (refused on v1, which has no such PDU)
- Native OS Notifications — Windows toast / macOS / Linux notifications with MIB-resolved trap names
- Filtering & Export — filter received traps and export to CSV
- CIDR Discovery — scan IP ranges for SNMP-responsive devices
- Ping — cross-platform, no elevated privileges required (pure Go)
- Traceroute — hop-by-hop route tracing, over IPv4 or IPv6
- Full operation history with timestamps and results
- Diff Comparison — side-by-side diff between any two query results
- Saved Queries — bookmark and re-run frequent queries
- Multiple targets with comma or newline separation
- Target Groups for organizing devices
- Labels — a device's label appears in operations, traps, history and events, with the address on hover
- Per-target Overrides — custom SNMP version, community, port per device
- Credential profiles — named communities and SNMPv3 users, each with its version, given to targets instead of the defaults — see the guide
- Connection Testing — verify reachability before operations
- Bind a preset — choose a dashboard preset when you add an equipment, or bind one later
- One-click privacy — hide all sensitive data for safe screenshots and demos
- Stable aliases — IPs become
Device-1,Device-2, etc. (consistent within a session) - Comprehensive masking — covers IP addresses, community strings, SNMPv3 credentials, hostnames, device descriptions, trap sources, debug logs
- Quick toggle —
Ctrl+Shift+Aor the checkbox in Settings → General - Visual indicator — pulsing
ANONbadge in the tab bar when active - Non-persistent — automatically disabled on restart to prevent accidental data hiding
- Credentials held by the operating system — the community string and SNMPv3 passphrases are sealed with a key kept by DPAPI on Windows, the Keychain on macOS, or a protected file on Linux, rather than stored beside the ciphertext
- Signed updates — an Ed25519-signed checksum manifest, bound to its release tag, verified before anything is applied; build provenance is attested
- Content from the network is escaped for every protocol it is written into — SMTP, mail headers, the syslog header — and exported CSV neutralises cells that a spreadsheet would run as a formula
- Presets cannot execute anything — a fixed widget and shape vocabulary, numeric OIDs only, no SVG, and map backgrounds named rather than carried
See the security policy for how to report a vulnerability.
- Dark / Light Theme with system detection or manual toggle
- Native Desktop Notifications — configurable per feature (traps, monitoring alerts)
- 5 Languages — English, French, German, Spanish, Chinese (auto-detected)
- Resizable Panels — adjustable MIB browser width (persisted)
- Keyboard Shortcuts — see table below
- Browser demo — the real interface on fixed data, at snmplens.com/demo.html
| Shortcut | Action |
|---|---|
Ctrl + 1 |
Operations tab |
Ctrl + 2 |
Traps tab |
Ctrl + 3 |
History tab |
Ctrl + 4 |
Monitor tab |
Ctrl + 5 |
Network / Discovery tab |
Ctrl + 6 |
Events tab |
Ctrl + 7 |
MIB editor tab |
Ctrl + 8 |
Dashboard tab |
Ctrl + , |
Open Settings |
Ctrl + B |
Show or hide the MIB panel |
Ctrl + Shift + A |
Toggle Anonymous Mode |
Ctrl + F |
Find and replace, in the MIB editor |
F5 |
Reload MIB files |
Esc |
Close the open dialog |
On macOS these use the Control key, not Command — except find and replace in the MIB editor, which accepts either.
| Layer | Technology |
|---|---|
| Framework | Wails v2 — Go backend + Web frontend in one binary |
| Backend | Go 1.26 — gosnmp, gosmi, SQLite, pro-bing |
| Frontend | Svelte 5 + Vite 8 |
| Charts | Chart.js with date-fns adapter |
| i18n | svelte-i18n |
| Database | SQLite (embedded, WAL mode) |
| Credentials | Key held by DPAPI (Windows), the Keychain (macOS) or a protected file (Linux) |
| Requirement | Version |
|---|---|
| Go | 1.26+ |
| Node.js | 20.19+ or 22.12+ (what Vite 8 requires) |
| Wails CLI | v2 |
Linux only — install GTK and WebKit development libraries:
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-devwails devStarts a hot-reload development server with live frontend updates.
wails buildThe compiled binary is output to build/bin/.
wails build -platform windows/amd64 # Windows
wails build -platform linux/amd64 -tags webkit2_41 # Linux
wails build -platform darwin/universal # macOS (Intel + Apple Silicon)SnmpLens updates itself from GitHub Releases. On startup (and via Settings → General → Updates → Check now) it queries the latest release and, if a newer version exists, shows a banner with the release notes.
Applying an update depends on how the app was installed:
| Platform / install | Action |
|---|---|
| Windows (installer) | downloads and runs SnmpLens-windows-amd64-setup.exe, then relaunches |
| Windows / Linux (portable binary) | replaces the running executable in place, then relaunches |
macOS, Linux .deb |
opens the download in the browser (manual install) |
The running version is embedded at build time via ldflags; local (wails build) builds report dev, which disables the update check.
Every release includes SnmpLens-checksums.txt (SHA-256 of all assets) and its Ed25519 signature SnmpLens-checksums.txt.sig. Before applying an update the app verifies the signature against the public key embedded in pkg/updater/verify.go, then verifies the asset's SHA-256 against the now-authenticated manifest — the "signed manifest" model Linux package repositories use.
-
Signing is mandatory. The release workflow fails when the key is missing, rather than publishing a release every installed copy would refuse.
-
The manifest names its tag on its first line (
version v1.2.3), so an older signed manifest cannot be replayed to install a previous, possibly vulnerable, release. -
Build provenance is attested, and can be checked without trusting this repository:
gh attestation verify SnmpLens-windows-amd64.exe --repo SnmpLens/SnmpLens
Rotating the signing key:
go run ./tools/updatersign keygen- Add the printed public key to
pkg/updater/verify.go(updaterPublicKeys). - Store the printed private key as the
UPDATER_PRIVATE_KEYsecret of thereleaseenvironment (Settings → Environments → release). That environment should carry a protection rule — a required reviewer or a tag policy — so a run has to be admitted before the key is handed to it.
Rotating the key takes two releases, because a copy already installed trusts only the keys embedded in the binary it is running:
- Add the new key to
updaterPublicKeysbeside the current one, and publish that release signed with the old key — every copy can verify it, and from then on trusts both. - Wait for it to be adopted. Nothing but time provides that, and it is the whole safety of the operation.
- Point
UPDATER_PRIVATE_KEYat the new key. Copies that installed step 1 accept it; copies that skipped it cannot update again and have to be reinstalled by hand. - Later, drop the retired key from the list — every key in it may install software on someone's machine, so one stays only while there are copies that trust nothing else.
SnmpLens
├── main.go # Wails entry point: the embedded files, handed to internal/app
├── internal/app/ # The methods the frontend calls, one file per feature
├── pkg/
│ ├── snmp/ # SNMP client: operations, walks, traps and informs, discovery
│ ├── simulator/ # Simulated SNMP agents: v1/v2c/v3, models, notifications, recordings
│ ├── mib/ # MIB loading (gosmi), diagnostics, editor analysis, dependency graph
│ ├── monitor/ # The poll clock, threshold episodes, counter wraps, overload guardrail
│ ├── preset/ # Dashboard presets: validation, cost, discovery, layout, thresholds, maps
│ ├── imagegate/ # The decode check a map background has to pass
│ ├── events/ # The event journal vocabulary
│ ├── notify/ # Alert routing: syslog, webhook, email, templates, durable outbox
│ ├── storage/ # SQLite (WAL): history, sessions, events, rules, outbox
│ ├── secrets/ # Credential store held by the operating system
│ ├── updater/ # Signed-manifest auto-update
│ ├── service/ tray/ autostart/ # Background operation: early preferences, tray icon, login entry
│ ├── network/ # Pure-Go ping and traceroute
│ └── netaddr/ # Address handling shared by snmp and network (IPv6, zones)
├── mibs/ # Bundled standard MIBs, extracted on first run
├── presets/ # Example dashboard presets, extracted on first run
└── frontend/src/
├── App.svelte # Tabbed shell, shortcuts, resizable MIB panel, file drop
├── MibPanel.svelte # MIB tree browser
├── OperationsPanel.svelte # SNMP operations and conceptual tables
├── TrapPanel.svelte # Trap and inform listener and sender
├── HistoryPanel.svelte # Query history and diff
├── MonitorPanel.svelte # Polling sessions and charts
├── DashboardPanel.svelte # Dashboards drawn from presets
├── DiscoveryPanel.svelte # CIDR discovery, ping, traceroute
├── EventsPanel.svelte # Event journal
├── MibEditorPanel.svelte # MIB editor
├── SettingsModal.svelte # Settings dialog; each section lives in settings/
├── stores/ # Svelte stores (state management)
├── utils/ # Helpers (CSV, formatting, layout, dashboard grouping…)
└── i18n/ # Translation files (en, fr, de, es, zh)
The Go backend exposes methods to the frontend via Wails bindings. SNMP operations run concurrently using goroutines for multi-target execution. Monitoring runs in Go, one goroutine per session, so it keeps going with the window closed. Data is persisted in an embedded SQLite database with WAL mode. Credentials are sealed with a key held by the operating system, which the frontend never sees.
The reasoning behind the less obvious decisions — and the measurements that settled them — is written down in CLAUDE.md.
Everything is set in the in-app Settings dialog (Ctrl + ,), in six sections:
- General — theme, language, SNMP timeout and retries, confirmation before a SET, data retention, notifications, update checks, Anonymous Mode
- MIBs — the MIB directory, which modules are loaded, missing dependencies and the dependency tree
- Presets — the dashboard preset library, what each one costs, and map backgrounds
- SNMP — default community, SNMPv3 credentials, and a connection test
- Notifications — alert destinations, routing rules, message templates and the delivery log
- Service — background operation, start at login, what to resume at startup, and the SET audit
Everything SnmpLens writes lives in the user config directory:
| OS | Path |
|---|---|
| Windows | %APPDATA%\SnmpLens\ |
| macOS | ~/.config/SnmpLens/ |
| Linux | ~/.config/SnmpLens/ |
| Item | Contents |
|---|---|
mibs/ |
The bundled MIBs, extracted on first run, plus everything you add |
presets/ |
Dashboard presets |
assets/ |
Map backgrounds |
mib-backups/ |
Backups taken before a bundled MIB is overwritten |
mib-drafts/ |
Unsaved MIB editor buffers |
monitoring.db |
SQLite: history, events, sessions, rules, destinations, outbox |
service.json |
The few preferences read before the window exists |
simulator.json |
Simulated devices (their passwords are in the system keychain) |
simulator-models/ |
Custom simulator models, and their icons |
SnmpLens carries its own SNMP agents, so everything in this README can be tried against devices running on your own machine — nothing to install and no equipment to touch.
- Click Simulator in the header, then New device.
- Pick a model — the Linux server comes first — and keep the address SnmpLens suggests: a loopback address of the device's own, such as
127.0.0.2. Loopback is the only kind of address a simulated device may answer on. - Save, then Start. The device answers v2c with the community
publicuntil you give it other identifiers in the editor's Access tab. - Add as target puts it in your target list, with its port and its identifiers, in the most secure version it answers.
- In Operations (
Ctrl + 1), pickifTablein the MIB tree and WALK it.
To see notifications arrive, start the listener in the Traps tab (Ctrl + 2), give the device a destination in its Notifications tab — a new one is SnmpLens on this machine, at the listener's port — and send any of its notifications from its menu in the list. The Data tab previews everything the device answers, before it is even started.
macOS answers on 127.0.0.1 alone unless aliases are added, so there devices are told apart by port: 127.0.0.1:1161 is a target of its own. The rest of the simulator — faults, writing, bench files, your own models — is under Simulated devices.
A credential profile is a named set of SNMP identifiers that targets are given instead of the defaults: a community for v1 or v2c, or an SNMPv3 user — name, security level, protocols and passphrases. A profile carries its SNMP version, because "this switch speaks v3 as ops-ro" is one statement.
Creating one. Settings → SNMP → Credential profiles, then Community profile or SNMPv3 user. Name it, fill in the identifiers, and tick the targets that use it under Targets using this profile. Test these identifiers tries them against one of your targets first; Apply keeps the profile, and saving the settings stores it.
From a target. A target that has identifiers of its own can turn them into a profile: in its overrides, Save as a profile creates one and gives it to the target.
Which identifiers a target uses comes from exactly one place, in this order:
- its profile, when it has one — a profile replaces the target's own identifiers rather than being layered under them;
- otherwise its own overrides — a community, a version, an SNMPv3 user;
- otherwise the default identifiers (Settings → SNMP), in the version chosen in the header.
Transport stays per target: the port, the timeout and the retries are never part of a profile. Deleting a profile sends its targets back to the default identifiers.
Monitoring follows. A monitoring session keeps the identifiers it was started with, and it is updated when the profile they came from changes: a rotated passphrase reaches every session built from the profile when the settings are saved, with nothing to rebind. Polling several targets that do not authenticate the same way starts one session per set of identifiers, and says so.
Traps. The trap listener authenticates received SNMPv3 notifications against the default user and every SNMPv3 profile at once — except those whose Accept SNMPv3 traps from this user is unticked, for a user only used to poll. The Traps tab lists who is heard, and marks a user the listener refused with the reason. v1 and v2c notifications are received whatever their community: there is nothing to accept or refuse.
Where they are kept. Communities and passphrases are sealed with a key the operating system holds — DPAPI on Windows, the Keychain on macOS, a file only your account can read on Linux — each profile's under its own ID, so renaming or reordering profiles never mixes them up. A profile still on MD5 or DES is flagged as using weak algorithms.
A preset is one JSON file describing what to poll on one kind of equipment and how to draw it. It picks from a fixed vocabulary — six widget kinds, four map shapes, numeric OIDs — and cannot describe anything of its own, which is what makes a preset written by someone else safe to open. The six examples SnmpLens writes out on first run are in presets/; reading one is the quickest way to start your own.
This one watches an access switch: two tiles on the left, a wall of its ports — discovered when it is bound — beside them, and the uplink traffic across the bottom.
{
"formatVersion": 1,
"name": "Access switch",
"author": "you",
"description": "Port states and uplink traffic. The ports are discovered when you bind it.",
"match": { "vendor": "Cisco", "sysObjectIdPrefix": ["1.3.6.1.4.1.9"] },
"intervalSec": 60,
"widgets": [
{ "kind": "value", "title": "Uptime", "oids": ["1.3.6.1.2.1.1.3.0"],
"layout": { "x": 0, "y": 0, "w": 3 } },
{ "kind": "value", "title": "Interfaces", "oids": ["1.3.6.1.2.1.2.1.0"],
"layout": { "x": 0, "y": 1, "w": 3 } },
{ "kind": "grid", "title": "Ports",
"oids": ["1.3.6.1.2.1.2.2.1.8.{#}"],
"discover": { "walk": "1.3.6.1.2.1.2.2.1.2" },
"labels": { "1": "up", "2": "down" },
"layout": { "x": 3, "y": 0, "w": 9, "h": 2 } },
{ "kind": "chart", "title": "Uplink traffic", "unit": "bit/s",
"oids": ["1.3.6.1.2.1.2.2.1.10.1", "1.3.6.1.2.1.2.2.1.16.1"],
"layout": { "x": 0, "y": 2, "w": 12, "h": 2 } }
]
}| Field | Required | What it is |
|---|---|---|
formatVersion |
yes | Always 1. A file from a newer version is refused with a sentence rather than misread |
name |
yes | What the library shows, up to 120 characters |
author, description |
no | The description can run to 600 characters |
match |
no | vendor and sysObjectIdPrefix: the equipment it is written for, which Detect uses to put it first |
intervalSec |
yes | How often to poll, from 5 to 86400 seconds |
widgets |
yes | From 1 to 40 of them |
| Field | What it is |
|---|---|
kind |
One of the six below |
title, unit |
What the widget is called, and what its readings are in — bit/s, % |
oids |
Numeric OIDs, never names — at most 500 across the whole preset |
labels |
A state number to a word, for status, grid and map: {"1": "up", "2": "down"} |
discover |
walk, the column to walk, and optionally max; the OIDs then carry {#} where the instance goes |
layout |
x 0–11, y 0–64, w 1–12 and h 1–12, in cells of a twelve-column grid; h defaults to 1 |
threshold |
min, max, forSeconds and alertEnabled — see below |
map |
The drawing, for a map widget only — see below |
| Kind | Draws | Readings |
|---|---|---|
value |
The latest reading | 1 |
rate |
A counter's per-second rate, corrected for wraps | 1 |
chart |
Readings over time | 8 |
status |
One reading as a state, named by the labels | 1 |
grid |
One cell per reading — a switch's port panel | 96 |
map |
A drawing whose shapes are readings | 96 |
Discovery. A widget can say where its instances come from instead of listing them: its OIDs carry {#}, and discover.walk names the column to walk — usually ifDescr. The walk happens once, when the preset is bound, and also names each instance, so a discovered port reads Gi0/1 rather than 8. A discovering widget takes at most 128 instances, or as many as its kind can draw, whichever is smaller. If the walk fails — the wrong community, an access list — the bind is refused and the message names the column.
Layout. Widgets are placed on a twelve-column grid, which divides into halves, thirds and quarters; widgets that are not placed find room on their own. A layout past the twelfth column, or two widgets on the same cells, is refused. Below 900 pixels the dashboard collapses to one column, in the author's order: top to bottom, then left to right.
Thresholds. A widget can carry a band, and binding the preset fills it in on the monitoring session as if you had typed it — yours to edit afterwards:
{ "kind": "status", "title": "Battery", "oids": ["1.3.6.1.2.1.33.1.2.1.0"],
"labels": { "1": "unknown", "2": "normal", "3": "low", "4": "depleted" },
"threshold": { "max": 2 } }{ "kind": "value", "title": "Charge", "unit": "%", "oids": ["1.3.6.1.2.1.33.1.2.4.0"],
"threshold": { "min": 30, "forSeconds": 300 } }The first is true on the first sample; the second has to hold for five minutes, so a UPS self-test raises nothing. With alertEnabled left out a band is evaluated; set to false, it is only drawn as a reference line. A band cannot go on a rate widget — it is compared with the raw reading, and a counter only goes up — and a reading carries one band at most.
Maps. A map is a drawing whose parts are readings — boxes, lines, labels and dots, placed in percentages of the widget, each optionally bound to one of the widget's own OIDs and coloured by what it reads. Deliberately not SVG: a preset picks among four shapes and cannot draw anything else.
{ "kind": "map", "title": "Front panel",
"oids": ["1.3.6.1.2.1.2.2.1.8.1", "1.3.6.1.2.1.2.2.1.8.2"],
"labels": { "1": "up", "2": "down" },
"map": {
"background": "rack-a.png",
"aspect": 6,
"shapes": [
{ "type": "rect", "x": 5, "y": 30, "w": 10, "h": 40, "oid": "1.3.6.1.2.1.2.2.1.8.1", "text": "1" },
{ "type": "rect", "x": 17, "y": 30, "w": 10, "h": 40, "oid": "1.3.6.1.2.1.2.2.1.8.2", "text": "2" },
{ "type": "line", "x": 27, "y": 50, "x2": 60, "y2": 50 },
{ "type": "label", "x": 70, "y": 50, "text": "Core" }
]
} }| Shape | Fields |
|---|---|
rect |
x, y for its top left, w and h for its size |
line |
From x, y to x2, y2 |
label |
x, y and the text, centred on the point |
dot |
x, y — a marker for something too small to draw |
Coordinates are percentages from the top left, 0 to 100, and nothing may run off the edge; a drawing holds up to 200 shapes. aspect is its width over its height, 0.2 to 12 — a rack front panel wants about eight to one. background is a file name, never a path: the picture itself is added in Settings → Presets — PNG, JPEG or GIF, up to 4 MiB — and stays on your machine.
- Settings → Presets: add the file, or drop it on the window. A preset with problems is kept and listed with them — each with the path to its field, such as
widgets[2].layout— and cannot be bound until they are fixed. The library states what a preset will cost before it runs: OIDs, cadence, requests and values a day, and how many readings carry a threshold. - In the target manager, pick the preset when you add an equipment, or Bind it to one already there. Detect asks the equipment for its
sysObjectIDand puts the presets whosematchnames it first — advice, not a filter. - Binding starts one monitoring session per equipment, polled with that equipment's own identifiers, and the Dashboard (
Ctrl + 8) draws it. Bind one preset to several equipments and the dashboard can draw them together.
A bound dashboard is a snapshot: editing or deleting the file afterwards changes nothing already bound, and binding again adopts a new version. A preset file is at most 1 MiB, and lives in presets/ in the configuration directory.
Each alert destination (Settings → Notifications) can carry its own subject and body, written as a template over a fixed vocabulary. The editor lists every variable with an example, and previews the result through the same path the destination uses.
| Syntax | Renders |
|---|---|
{{variable}} |
The variable's value, or nothing |
{{variable|default}} |
The value, or default when it is empty |
{{#variable}}…{{/variable}} |
What is between, only when the variable has a value |
{{lb}} |
Two literal opening braces |
| Variable | What it is | Example |
|---|---|---|
severity |
Severity name (info, warning, minor, major, critical) | major |
severityUpper |
Severity in capitals, for a subject line | MAJOR |
severityNumber |
Severity as a number from 1 to 5 | 4 |
category |
Event family: trap, threshold, reachability, system | threshold |
kind |
Exact event kind, stable across versions | threshold.opened |
state |
oneshot, open or resolved |
open |
summary |
The one-line description SnmpLens writes | ifInOctets above 900 on 10.0.0.1 |
source |
Address of the device concerned | 10.0.0.1 |
oid |
OID concerned, when there is one | 1.3.6.1.2.1.2.2.1.10.1 |
value |
Measured value, when the event carries one | 912.5 |
sessionName, sessionId |
The monitoring session, by the name you gave it and by its identifier | WAN Paris |
dedupKey, corrId |
What recognises a repeat of the same incident, and ties a resolution to its alert | |
id, seq |
The event's unique identifier, and its number in the journal | 1042 |
ts, tsLocal |
The time in UTC (ISO 8601), and in this machine's time zone | 2026-09-01T09:12:44Z |
hostname, appVersion |
The machine running SnmpLens, and its version | workstation |
sinkName |
The destination this message is going to | NOC mail |
A subject and a body for email:
[{{severityUpper}}] {{summary}}
{{summary}}
Source: {{source}}
{{#oid}}OID: {{oid}}
{{/oid}}{{#value}}Value: {{value}}
{{/value}}Session: {{sessionName|none}}
{{tsLocal}} — SnmpLens {{appVersion}} on {{hostname}}
A webhook's payload can be the template itself, which is how SnmpLens talks to Slack, Teams or Alertmanager. Values are then escaped as JSON string fragments while your own punctuation is left alone — a trap's OID arrives from the network, and one quote in it would otherwise make a different document — and the result is checked as JSON before it is posted, and against a sample of every kind of event when you save. For Slack:
{ "text": "*{{severityUpper}}* {{summary}}\n{{source}} · {{tsLocal}}" }No credential goes in a template: secret, password, token, community, authpass, privpass and apikey are reserved names. A destination's one secret — the SMTP password, the bearer token, the syslog client key — is drawn into a header value or the URL with {{secret}}, which is how the address of a Slack or Teams webhook, which is its credential, stays in the keychain rather than in the configuration.
Two rules keep a template safe: substituted text is never scanned again, so a trap OID reading {{secret}} comes out as those characters; and masking is applied to the event before the template sees it.
The standard MIBs are bundled and extracted on first run; vendor MIBs are yours to add.
- Import — in Settings → MIBs, or by dropping the files on the window. A file that is not a MIB at all — an HTML page saved by mistake, a PDF, a ZIP, a file in UTF-16 — is named as such rather than reported as a failed load.
- Enable and disable — any module can be switched off without being deleted; Enable All and Disable All do the lot. What is loaded is what the MIB tree shows, and what names OIDs everywhere else.
- Dependencies — Missing dependencies names each module that one you loaded imports and that is not there, with the symbols that were needed from it; the dependency tree shows what imports what.
- Diagnose — when a MIB does not load, SnmpLens says why: the stage it stopped at (read, content, parse, imports, build, semantic), the line and column with an excerpt, and the chain followed to its root when a dependency fails too. A module can also load and be broken — an import you do not have resolves to nothing — which is why Diagnose is offered on successes too.
- Edit — the MIB editor (
Ctrl + 7) edits the MIBs in your directory, or opens one from anywhere and saves it in, with highlighting, syntax and semantic checks as you type, an outline, and fixes for missing IMPORTS. A bundled MIB is backed up before it is overwritten and can be restored.
MIBs live in mibs/ in the configuration directory, which is the single source of truth once SnmpLens has started.
The Simulator indicator in the header opens the simulated devices: SNMP agents running on this machine, at loopback addresses only — 127.x.x.x or ::1 — answering v1, v2c and v3 (the whole USM) from a catalogue of models, or from your own (below). A device can be started and stopped, restarted (uptime and counters from zero, and coldStart if it sends one on starting), duplicated, made to send any of its notifications on demand, and added as a target in one click, with its port and identifiers.
A bench moves as a file. Export all…, or a device's export button, writes JSON, and SnmpLens asks every time whether to put the passwords in it — the communities and passphrases, which otherwise stay in the system keychain. The file says which ("secrets": "included" or "omitted"). Import devices… makes each device new on this machine: its own ID and engine ID, and another address when its own is taken. A device that cannot be made here — of a custom model that is not installed, say — is refused on its own; devices imported without their passwords start once they are given them in the editor.
Faults make a device misbehave while it runs, without restarting it — its uptime and counters are what a fault is tested against: a latency (with jitter), a share of requests lost, a device that answers nothing at all, a share answered with genErr or tooBig, and counters running 10, 100 or 1000 times faster, so that a Counter32 wraps in minutes. They are what SnmpLens's reachability alerts, overload guardrail and counter-wrap arithmetic are tested against, and they are kept with the device. A device can also be given its own sysLocation and sysContact, and set to start with SnmpLens.
A device can be written to. Give it a write community (v1 and v2c — a password, kept in the keychain like the community), or tick Can write (SET) on an SNMPv3 user, and a SET changes what it answers until it restarts, as a running configuration does until it is saved. A table row is created and destroyed through its RowStatus, as the MIBs loaded in SnmpLens describe it, so the row editor works against a simulated device as it does against a real one. A manager that only reads is answered noAccess, and Add as target uses the write access when the device has one. The editor is in tabs — the device, its access, its notifications, its data, its faults — and a tab holding something to fix is marked.
A device's data can be shaped. The Data tab gives a model its size where it has one — the ports of a Catalyst, the disks of the Synology, the outlets of the rack PDU, the processors of the Linux server — and takes values of the device's own, OID by OID, in the types a model file uses: each takes the place of the model's value at its OID, or adds an instance the model does not have. A preview below shows what the device would answer, from the settings as they are on screen, saved or not: each object with its name from the loaded MIBs (IF-MIB::ifDescr.3) and how its value behaves — static, counter, gauge, uptime… — read again every few seconds so that what moves is seen moving, filtered by OID subtree or by MIB name, in columns whose width can be dragged. The Faults tab applies faults from the editor, as the list's button does. The communities and passphrases are masked in the editor, and shown by the eye beside them — except in Anonymous Mode.
The simulator's catalogue can be extended with models of your own: a JSON file saying what a device answers, imported from the model picker (Import models…) on its own, or in a ZIP archive that also carries the model's icon — PNG, JPEG or GIF, up to 512 × 512 px. An archive may hold several models. A model imported again replaces the one kept, and the devices made from it restart.
{
"kind": "snmplens-simulator-model",
"formatVersion": 1,
"id": "acme-crac",
"name": "Acme CRAC-40 cooling unit",
"vendor": "Acme",
"category": "environment",
"icon": "acme-crac.png",
"system": { "descr": "Acme CRAC-40 controller, firmware 3.2.1", "objectId": "1.3.6.1.4.1.32473.1.40" },
"interfaces": [{ "descr": "eth0", "speedMbps": 100, "up": true, "inOctetsPerSec": 400, "outOctetsPerSec": 900 }],
"objects": [
{ "oid": "1.3.6.1.4.1.32473.2.2.1.1.{#}", "instances": [1, 2, 3], "type": "Integer32", "value": "{#}" },
{ "oid": "1.3.6.1.4.1.32473.2.2.1.2.{#}", "instances": [1, 2, 3], "type": "OctetString", "value": "Sensor {#}" },
{ "oid": "1.3.6.1.4.1.32473.2.2.1.3.{#}", "instances": [1, 2, 3], "type": "Integer32", "gauge": { "min": 180, "max": 260 } },
{ "oid": "1.3.6.1.4.1.32473.2.4.0", "type": "Counter64", "counter": { "perSecond": 1, "start": 31536000 } },
{ "oid": "1.3.6.1.4.1.32473.3.1.0", "type": "OctetString", "value": "Return air above 27 °C", "notifyOnly": true }
],
"notifications": [
{ "name": "acmeHighTemperature", "oid": "1.3.6.1.4.1.32473.0.1",
"objects": ["1.3.6.1.4.1.32473.2.2.1.3.2", "1.3.6.1.4.1.32473.3.1.0"] }
]
}| Field | What it says |
|---|---|
id |
Lower-case letters, digits and hyphens; the model is listed as custom:<id> |
category |
server, network, security, wireless, storage, power, printing, environment or other |
icon |
A file in the same ZIP; a model imported alone keeps the icon it already had |
system |
descr and objectId (sysDescr, sysObjectID) are required; contact, location, services are not. sysName is the device's name |
interfaces |
IF-MIB's tables: descr, name, alias, type (IANAifType), mtu, speedMbps, up, adminDown, and the rates inOctetsPerSec, outOctetsPerSec, errorsPerSec the counters move at |
objects[].type |
Integer32, OctetString, ObjectIdentifier, IpAddress, Counter32, Gauge32, Unsigned32, TimeTicks, Counter64, Opaque |
objects[] behaviour |
Exactly one of value, hex (octets), uptime, secondsUp, gauge (min, max, periodSec) and counter (perSecond, start, swing, periodSec) |
instances |
A table's rows: {#} in the OID, and in a string value, becomes each; "value": "{#}" is the row's own index |
notifyOnly |
An accessible-for-notify object: carried by notifications, never answered to a request |
notifications |
The model's own, beside coldStart, warmStart and authenticationFailure, which every device sends |
Some OIDs are the agent's own and no model may answer them: SNMPv2-MIB's snmp group on every device, and for SNMPv3 snmpEngine, the MPD and USM statistics and snmpUnknownContexts. They are read live from what the agent counts as it answers — requests, varbinds, a refused community, notifications sent.
A file is checked in full when it is imported — unknown fields, types, ranges, an OID given twice, an OID the agent keeps — and a refusal names the field or the OID. The example is pkg/simulator/testdata/custom-model.json; 32473 is the enterprise number RFC 5612 reserves for documentation.
A model can also be spread over the files of one folder — a package — so that each file says one thing, and so that a device can answer what a real one answered, recorded:
acme-gateway/
├── model.json what the device is: the format above, "system" optional once a walk records one
├── oids.json objects, as { "objects": [ … ] } — or several files in oids/
├── traps.json notifications, as { "notifications": [ … ] }
├── walks/ what a real device answered: snmpsim .snmprec, or snmpwalk -On output
│ └── gateway.snmprec
└── acme-gateway.png the icon model.json names
Zip the folder and import it; one archive may hold several packages and lone models. Record a walk with snmpwalk -v2c -c public -On -Ox <host> .1.3.6.1 > walks/device.walk — plus a walk of .1.0.8802 if the device answers LLDP — or with snmpsim's recorder. -On keeps the OIDs numeric, which is how a walk is read; -Ox keeps an OCTET STRING's octets rather than the text a MIB's DISPLAY-HINT makes of them, so a MAC address stays six octets.
Three rules put the files together:
- What is written wins over what was recorded, OID by OID: an object in an oids file replaces the value a walk recorded at the same OID — which is how a recorded constant is made to move, or a recorded value corrected. An OID written twice is still an error. When
model.jsongivesinterfaces, IF-MIB is made from them and the walks' own is left out. - The agent's own objects stay behind. The snmp group and everything under
1.3.6.1.6.3— the recorded device's engine, its USM users, VACM groups and community strings — are never served, and the import says how many were left out. - The system group is made, never replayed. sysName is the simulated device's name and sysUpTime its own uptime; sysDescr, sysObjectID, contact, location and services are what the walks recorded, except where
model.json'ssystemsays otherwise.
Recorded counters move. Each starts at the value recorded and grows at the rate it had averaged since the device booted — its value over the sysUpTime recorded beside it — swinging around that rate as traffic does. hrSystemUptime and hrSystemDate are the device's own; everything else answers what was recorded. A line of a walk that cannot be read is left out and counted, and the import says where the first one was. A walk is at most 16 MB, and a package's walks record at most 65 536 objects between them. The example is pkg/simulator/testdata/package.
Record a device… in the model picker walks a real device — .1.3.6.1, then .1.0.8802 for LLDP — with the identifiers its target uses, and keeps what it answered as a package: a model like any other, from which a device answers what the real one did, its counters moving. The recorded device's own agent — its snmp group, engine, USM users, VACM groups and community table — is never written. A recording can be stopped, and then keeps nothing; a device answering more than 65 536 objects is refused rather than cut short.
Export (beside a custom model's delete button) writes any custom model to a ZIP as a package folder, with its icon. It is how a recording is edited — add an oids.json beside its walk to make a recorded constant move, or a traps.json to give it notifications — and imported again, or passed on.
MIT — Geoffrey Lecoq
















