Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Erlang CI
on:
pull_request:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.gitignore'
push:
branches: [master]
paths-ignore:
- '**.md'
- 'LICENSE'
- '.gitignore'
permissions:
contents: write
pull-requests: write
jobs:
ci:
uses: Taure/erlang-ci/.github/workflows/ci.yml@v2.1.1
with:
otp-version: '29'
enable-ex-doc: true
# fmt/xref/dialyzer/eunit/summary are on by default. audit + sbom are left
# off (their defaults): with this repo's `{vsn, git}` deps, rebar3_sbom
# badarg's on the `git` atom version and rebar3_audit crashes formatting a
# LOW-advisory glyph under OTP 29. Re-enable once the plugins handle it.
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Release
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
release:
uses: Taure/erlang-ci/.github/workflows/release.yml@v2.1.1
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
_build/
doc/
*.crashdump
erl_crash.dump
86 changes: 49 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,74 @@
# Nova Liveboard
# nova_liveboard

Real-time BEAM VM dashboard for [Nova](https://github.com/novaframework/nova) — the Erlang equivalent of Phoenix LiveDashboard.
Real-time BEAM VM dashboard for [Nova](https://novaframework.org) - a
self-hosted, dependency-light "mission control" for a running node. Built on
**Nova + [Datastar](https://data-star.dev)**: server-rendered HTML, live
updates over SSE, no JavaScript build step, no off-origin requests.

Built on [Arizona](https://github.com/Taure/arizona_core) for live differential rendering over WebSocket.
## What it shows

## Pages
| Page | Live view |
|------|-----------|
| Overview | OTP/ERTS, schedulers, capacity gauges, memory breakdown |
| Metrics | sparklines (memory, run queue, IO) + scheduler utilisation |
| Processes | top processes by memory / reductions / message queue |
| Requests | every HTTP request the node handles, streaming in live, with an opt-in deep trace of the **processes each request spawns** |
| Supervisors | the live supervision tree per application |
| Applications / ETS / Ports | running apps, ETS tables, open ports |
| Database / Schemas | Kura repos, pools and schemas (when Kura is present) |

| Page | Description | Refresh |
|------|-------------|---------|
| **System** | OTP release, uptime, schedulers, memory breakdown with usage bars | 2s |
| **Processes** | Top 50 processes by memory/reductions/message queue, sortable | 2s |
| **ETS** | All ETS tables with type, protection, size, memory, owner | 3s |
| **Applications** | Running applications with versions | 5s |
| **Ports** | Open ports with I/O stats | 3s |
| **Supervisors** | App selector with supervision tree visualization | 5s |
| **Metrics** | Live sparkline charts (memory, processes, IO, run queue) + scheduler utilization bars | 2s |
The vitals deck across the top stays live on every page.

## Setup
## Install

Add to your Nova application's `rebar.config`:
Add it as a Nova app in your release and mount it via `nova_apps`:

```erlang
{deps, [
{nova_liveboard, {git, "https://github.com/novaframework/nova_liveboard.git", {branch, "master"}}}
{nova_liveboard,
{git, "https://github.com/novaframework/nova_liveboard.git", {branch, "master"}}}
]}.
```

Add `nova_liveboard` to your application's dependencies in your `.app.src`:

```erlang
{applications, [kernel, stdlib, nova, nova_liveboard]}
%% your sys.config
{your_app, [{nova_apps, [nova_liveboard]}]}.
{nova_liveboard, [{prefix, "/liveboard"}]}.
```

Configure the liveboard in your `sys.config`:
Open `/liveboard`.

## Request tracing

The Requests page needs the tracing plugin, registered globally so it sees
every route on the node:

```erlang
{nova, [
{nova_apps, [
#{name => nova_liveboard, prefix => "/liveboard"}
]}
]}
{nova, [{plugins, [
{pre_request, nova_liveboard_trace_plugin, #{}},
{post_request, nova_liveboard_trace_plugin, #{}}
]}]}.
```

Visit `http://localhost:8080/liveboard` in your browser.
The request feed (method, path, status, duration, reductions, handler) is
always on and cheap. "Arm next 10" turns on scoped process tracing for the next
few requests, so you can open a request and see the exact tree of processes it
spawned - overhead is only paid while armed.

## Dependencies
## Configuration

- [Nova](https://github.com/novaframework/nova) — Erlang web framework
- [Arizona Core](https://github.com/Taure/arizona_core) — Live view engine with compile-time template optimization
- [Arizona Nova](https://github.com/Taure/arizona_nova) — Bridge between Arizona and Nova (WebSocket controller, PubSub)
| Key | Default | Meaning |
|-----|---------|---------|
| `prefix` | `"/liveboard"` | mount path |
| `refresh_ms` | `2000` | repaint interval for polled streams |
| `request_buffer` | `200` | recent requests retained |

## Architecture
## Privacy

- **Data layer** (`nova_liveboard_data`) — Pure functions collecting VM metrics, supervision trees, scheduler wall time deltas, sparkline point generation
- **Views** — Arizona views with `arizona_parse_transform` for compile-time template optimization
- **WebSocket** — Thin wrapper around `arizona_nova_websocket` with `flatten_reply` to bridge Arizona's list-based frame replies to Nova's single-frame `handle_ws` callback
- **Routing** — Nova router with WebSocket route defined after `/:page` to ensure `routing_tree` matches the exact `/live` path before the binding
Fonts, CSS and datastar.js are all self-hosted under `priv/static/assets`, and
the dashboard sends a strict `Content-Security-Policy` of `default-src 'self'`.
Nothing is fetched off-origin.

## License
## Licence

Apache-2.0
Apache-2.0.
38 changes: 38 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[changelog]
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
trim = true

[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^docs", group = "Documentation" },
{ message = "^refactor", group = "Refactor" },
{ message = "^test", group = "Testing" },
{ message = "^chore\\(release\\)", skip = true },
{ message = "^chore", group = "Miscellaneous" },
{ message = "^ci", skip = true },
]
protect_breaking_commits = false
tag_pattern = "v[0-9].*"
sort_commits = "oldest"
Loading
Loading