English | Bahasa Indonesia
A Node-RED-style visual flow editor + runtime, written in Rust. A single
binary: an axum server serves the UI (Svelte 5 + @xyflow/svelte, embedded),
the REST API, the SSE debug stream, and runs the node execution engine in the
same process. Flows are stored in SQLite and can be exported/imported as JSON.
Built with tokio-modbus, rumqttc and regex for the device I/O, and axum + rust-embed + Svelte/xyflow for the editor.
- Canvas editor drag-and-drop (xyflow): node palette, wiring, per-node config inspector, live debug panel.
- Runtime: the Deploy button runs the graph as interconnected tokio tasks
wired by channels — messages (
msg) flow along the wires. - Built-in nodes:
Node Role injectsource: emit once / periodically (timestamp/string/number/json) debugsink: show the payload in the debug panel catch/statusemit a message on any node error / status change link-in/link-outvirtual wires across the flow (by name) functiontransform the payload via rhai / JavaScript (boa) / WASM (wasmi: C/C++/Rust/Go) switchroute a message to one of two outputs by a condition changeset / delete / move a message property rangescale / clamp / wrap a numeric property templaterender a {{ path }}template into a propertyfilterreport-by-exception: pass on only when the value changed delaydelay each message, or rate-limit the stream triggerpass a message, then send another after a delay split/joinbreak a message into parts / reassemble them sort/batchsort an array / group messages into arrays modbus-readmaster: read holding/input/coils/discrete → emit modbus-writemaster: write a register/coil, or a 32-bit float/uint over two registers, from the payload modbus-slaveserver: act as a Modbus device polled by an external master sysinfosource: host CPU%, memory, and local IP (the resource snapshot) serial-asciiread ASCII lines from a serial port (e.g. a scale) + write commands; regex, byte-map, parity/stop/data bits mqtt-in/mqtt-outsubscribe / publish MQTT - Modbus transports: TCP, RTU-over-TCP, RTU-serial (master); TCP + RTU-serial (slave/server).
- MQTT: protocol 3.1.1 or 5. Transports: TCP, TLS, WebSocket (
ws), and secure WebSocket (wss). TLS/WSS use the system root certificates, or a custom CA / client certificate (PEM) for self-signed brokers and mutual TLS. Optional broker username/password. Configurable QoS (0/1/2), publish retain flag, and keep-alive. Optional will / birth / close messages (close is published on a graceful redeploy or shutdown). Nodes sharing aclient_idreuse one connection. - Auth (Node-RED-style): username/password sign in (in-memory session token).
- Localization: English (default) + Indonesian, switchable in the toolbar. The message log (node status) is localized too: the backend emits stable status codes and the UI translates them to the active language.
- Export/Import flows as JSON (
{ name, graph }).
- Backend: Rust, axum 0.8, tokio, sqlx (SQLite), rust-embed.
- Node runtime: tokio-modbus 0.17, tokio-serial, rumqttc, rhai, regex.
- Frontend: Svelte 5,
@xyflow/svelte, Vite.
The UI is embedded from ui/dist, so build the UI before building the binary:
# 1. Build the UI → ui/dist
cd ui && npm install && npm run build && cd ..
# 2. Build + run the binary (embeds ui/dist)
cargo runOpen http://localhost:3000. Default login admin / admin — set FLOW_USER /
FLOW_PASS for any non-local deployment (the server warns when the password is
left at the default). Repeated failed logins are temporarily rate-limited.
cargo run # backend on :3000
cd ui && npm run dev # UI on :5173, proxies /api → :3000| Env | Default | Description |
|---|---|---|
FLOW_BIND |
0.0.0.0:3000 |
HTTP bind address |
FLOW_DB |
sqlite://flow-engine.db |
SQLite URL |
FLOW_USER |
admin |
login username |
FLOW_PASS |
admin |
login password |
RUST_LOG |
info |
tracing filter |
All /api/* require Authorization: Bearer <token> except /api/login. SSE
takes the token via ?token= (EventSource cannot set headers).
POST /api/login·POST /api/logoutGET /api/nodes— node type catalog (palette + inspector fields)GET/POST /api/flows·GET/PUT/DELETE /api/flows/{id}GET /api/flows/{id}/export·POST /api/flows/importPOST /api/flows/{id}/deploy·POST /api/runtime/stop·GET /api/runtime/statusGET /api/debug/stream— SSE: debug output + node status
{
"name": "example",
"graph": {
"nodes": [
{
"id": "n1",
"type": "inject",
"position": { "x": 0, "y": 0 },
"config": { "mode": "interval", "interval_ms": 1000 }
}
],
"edges": [{ "id": "e1", "source": "n1", "target": "n2" }]
}
}The schema mirrors xyflow; it is not byte-compatible with a real Node-RED
flows.json.
- The function node has a Language dropdown:
- rhai (default) / js (boa) — a script that sees
payloadandtopic, mutates them, and the node emits the result. Example:payload = payload * 2; - wasm (wasmi) — upload a compiled
.wasmmodule (C/C++/Rust/Go), run sandboxed. ABI + a C example in examples/.
- rhai (default) / js (boa) — a script that sees
- Only one flow is active at a time; Deploy replaces the running graph.
- Modbus slave: holds an in-memory register/coil map; external master writes
update the map and emit a
{ fc, address, value }message on the node output. Feed messages into the node to update the map the master reads.