Publish the documentation site: docs.fluksio.com
A zensical site under docs/, served by a new `docs` compose service behind Traefik, built with --strict in CI. Same pattern the sibling n3xd workspace uses. Getting started splits the way the landing page does — one path is `pip install fluksio` and a training script, the other is a Docker stack and an afternoon in the browser — because the two audiences will not spend the same amount of time. Everything after that is shared: the concepts, the web interface (app and portal), the CLI and the API, and a reference for node types, payload types and configuration. The three flow guides move here from the docs submodule rather than being copied, so there is one version of them. Styling mirrors DESIGN-GUIDELINES.md: the app's token palette remapped onto Material's variables in both schemes, Inter, the 16px panel radius, and the one terracotta accent spent on the facility lane of the audience split. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M7Xv3cJEW5c8AXxn2hoojV
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# Configuration
|
||||
|
||||
Every setting comes from the environment, or from an env file. Which file
|
||||
depends on how the installation was started:
|
||||
|
||||
| Started with | Reads |
|
||||
|---|---|
|
||||
| `fluksio serve` | `env` inside the data directory (`$FLUKSIO_ENV_FILE`) |
|
||||
| the Docker stack | `.env` beside `docker/` |
|
||||
|
||||
Anything already exported wins over the file.
|
||||
|
||||
## Storage
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `DATA_DIR` | `flow-data` (`~/.fluksio` via the CLI) | everything below derives from this |
|
||||
| `DATABASE_URL` | SQLite in `DATA_DIR` | any SQLAlchemy URL |
|
||||
| `FLOWS_DIR` | `$DATA_DIR/flows` | the git repository holding flows |
|
||||
| `SECRETS_FILE` | `$DATA_DIR/secrets.enc` | encrypted credentials, deliberately outside the repo |
|
||||
| `ALERTS_FILE` | `$DATA_DIR/alerts.json` | channels and rules |
|
||||
| `PANELS_FILE` | `$DATA_DIR/panels.json` | wall-panel pairings |
|
||||
| `OAUTH_PRIVATE_KEY_FILE` | `$DATA_DIR/oauth-key.pem` | signs agent and worker tokens |
|
||||
| `CLOUD_CONFIG_FILE` | `$DATA_DIR/cloud.json` | the portal enrolment, if any |
|
||||
|
||||
Set `DATA_DIR` and the rest follow. Set one explicitly and it wins — which is
|
||||
what the container images do to pin everything onto `/data`.
|
||||
|
||||
!!! warning "The four files that must be on persistent storage"
|
||||
|
||||
`secrets.enc`, `alerts.json`, `panels.json` and `oauth-key.pem` are written
|
||||
at runtime. In a container, anything not on a volume lands in the writable
|
||||
layer and is lost on the next rebuild — un-pairing every screen and
|
||||
revoking every agent.
|
||||
|
||||
## State
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `REDIS_HOST` | unset | without it, flow state lives in memory and does not survive a restart |
|
||||
| `REDIS_PORT` | `6379` | |
|
||||
|
||||
Flow state is the last value of every message, node memory, and the run queue.
|
||||
Redis here is persistence, not a cache — run it with append-only persistence
|
||||
on.
|
||||
|
||||
## Identity and access
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `SECRET_KEY` | generated | signs sessions and derives the secrets-store key |
|
||||
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `11520` (8 days) | |
|
||||
| `FIRST_SUPERUSER` | — | absent means the CLI creates one on first run |
|
||||
| `FIRST_SUPERUSER_PASSWORD` | — | absent means one is generated and printed once |
|
||||
| `DOMAIN` | `localhost` | what the API and OAuth issuer are built from |
|
||||
| `FRONTEND_HOST` | `http://localhost:5173` | used in mails, OAuth metadata and panel pairing links |
|
||||
| `BACKEND_CORS_ORIGINS` | `[]` | comma-separated; `FRONTEND_HOST` is always allowed |
|
||||
|
||||
!!! danger "Rotating `SECRET_KEY`"
|
||||
|
||||
The secrets store is encrypted with a key derived from it. Change it and
|
||||
the store stops decrypting, and every session is signed out. Re-enter your
|
||||
secrets, or plan the rotation properly.
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `ENVIRONMENT` | `local` | `local`, `staging` or `production` |
|
||||
| `PRIVATE_API_ENABLED` | `false` | unauthenticated test-only endpoints; needs `ENVIRONMENT=local` too |
|
||||
|
||||
`production` closes `/docs`, `/redoc` and the OpenAPI document, because the
|
||||
schema enumerates every endpoint the installation serves — including the paths
|
||||
webhook nodes mounted at runtime. It also turns a `changethis` secret from a
|
||||
warning into a refusal to start.
|
||||
|
||||
## The engine
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FLOW_MAX_WORKERS` | `4` | node-code subprocesses run in parallel |
|
||||
| `FLOW_NODE_TIMEOUT` | `30.0` | seconds a node may run, unless it sets its own |
|
||||
| `OBS_RETENTION_DAYS` | `30` | how long metrics, events and run records are kept |
|
||||
|
||||
## Agents
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `MCP_ENABLED` | `false` | opens the `/mcp` endpoint **and** OAuth client registration |
|
||||
| `MCP_TOKEN_EXPIRE_MINUTES` | `60` | an agent's token is a bearer secret held by a program |
|
||||
| `MCP_REFRESH_EXPIRE_DAYS` | `30` | |
|
||||
| `OAUTH_CODE_EXPIRE_SECONDS` | `60` | |
|
||||
|
||||
See [Agents over MCP](../code/agents.md).
|
||||
|
||||
## Mail
|
||||
|
||||
Needed for password-reset mails. Without `SMTP_HOST` and `EMAILS_FROM_EMAIL`,
|
||||
mail is simply off.
|
||||
|
||||
| Variable | Default |
|
||||
|---|---|
|
||||
| `SMTP_HOST` | — |
|
||||
| `SMTP_PORT` | `587` |
|
||||
| `SMTP_USER` / `SMTP_PASSWORD` | — |
|
||||
| `SMTP_TLS` / `SMTP_SSL` | `true` / `false` |
|
||||
| `EMAILS_FROM_EMAIL` | — |
|
||||
| `EMAILS_FROM_NAME` | `Fluksio` |
|
||||
| `EMAIL_RESET_TOKEN_EXPIRE_HOURS` | `48` |
|
||||
|
||||
## Monitoring
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `SENTRY_DSN` | — | error reporting, if you want it |
|
||||
|
||||
## Health check
|
||||
|
||||
`GET /api/v1/utils/health/` is a *deep* check: it fails when the event loop is
|
||||
wedged or the state backend is gone, not just when the process is up. That is
|
||||
what the container healthcheck probes, and what an autoheal sidecar restarts
|
||||
on.
|
||||
|
||||
## See also
|
||||
|
||||
- [The `fluksio` command](../code/cli.md) — what the data directory holds
|
||||
- [Getting started: facility automation](../getting-started/facility-automation.md)
|
||||
@@ -0,0 +1,286 @@
|
||||
# Node types
|
||||
|
||||
Every type the canvas can place. Each is configured by filling in a form the
|
||||
editor generates from its parameter schema, so they all behave the same way.
|
||||
|
||||
Anything here could be written as a **Function** node — that is what the
|
||||
function node is for. These exist because the same handful of shapes account
|
||||
for most of a real installation, and a rule you fill in is easier to read on a
|
||||
canvas, and to change, than five lines of code repeated eighty times.
|
||||
|
||||
`GET /flows/node-types` returns this list with each type's full parameter
|
||||
schema, including any [connectors](#connectors) installed on your instance.
|
||||
|
||||
## Function
|
||||
|
||||
**`python`** — your own code, run on every incoming message.
|
||||
|
||||
The one type with a source file. Its arguments are its input ports and its own
|
||||
settings; its return value is a dict keyed by output ports. See
|
||||
[Writing node code](../code/nodes.md).
|
||||
|
||||
Settings on a Function node are free-form: you add them by name, and they
|
||||
arrive as keyword arguments.
|
||||
|
||||
## Integrations
|
||||
|
||||
### MQTT
|
||||
|
||||
**`mqtt`** — subscribe to topics, or publish what arrives on its inputs.
|
||||
|
||||
A node with *outputs only* subscribes; a node with *inputs* publishes.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `topic` | `*` | one topic for every port, or `{"message": "some/topic"}` per port |
|
||||
| `broker_host` | `localhost` | |
|
||||
| `broker_port` | `1883` | |
|
||||
| `username` / `password` | — | `password` takes a secret reference |
|
||||
| `client_id` | — | |
|
||||
| `qos` | `0` | 0, 1 or 2 |
|
||||
| `retain` | `false` | on published messages |
|
||||
| `keepalive` | `60` | seconds |
|
||||
|
||||
Nodes sharing a broker share one connection.
|
||||
|
||||
### HTTP
|
||||
|
||||
**`http`** — receive data on a webhook, or send it to a URL.
|
||||
|
||||
Outputs only makes it a **webhook**: the engine mounts a route at
|
||||
`/hooks/{flow}/{url}/{secret}` while the flow runs. Inputs make it a **sender**.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `url` | — | the route path in webhook mode, the full URL in sender mode |
|
||||
| `method` | `POST` | `GET` or `POST` |
|
||||
| `timeout` | `30` | seconds, sender mode |
|
||||
| `headers` | `{}` | |
|
||||
| `secret` | — | shared secret appended to the webhook URL; takes a secret reference |
|
||||
|
||||
!!! warning "A webhook with no secret is open to anyone who can reach the host."
|
||||
|
||||
The canvas flags this as an advisory issue rather than stopping the flow,
|
||||
because a webhook on a private network is a legitimate thing to want.
|
||||
|
||||
### InfluxDB
|
||||
|
||||
**`influxdb`** — write measurements to a bucket, or read them back.
|
||||
|
||||
| Setting | Notes |
|
||||
|---|---|
|
||||
| `url`, `token`, `org`, `bucket` | connection; `token` takes a secret reference |
|
||||
| `write_precision` | `ns`, `us`, `ms` (default) or `s` |
|
||||
| `query_range` | default window for queries, e.g. `-1h` |
|
||||
| `writes` | per-input: `measurement`, `field`, `tags` |
|
||||
| `queries` | per-output: `measurement`, `field`, `tags`, `range`, `aggregation` |
|
||||
|
||||
```json
|
||||
{
|
||||
"writes": {
|
||||
"living_temperature": {
|
||||
"measurement": "environment",
|
||||
"field": "temp_c",
|
||||
"tags": {"room": "living"}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Query passthrough.** An incoming message holding a `flux` key is run as
|
||||
written, and the rows come back on the first output port as
|
||||
`{"rows": [{ts, value, field, measurement, tags}], ...}`.
|
||||
|
||||
That is what keeps a database node a database node: it holds the credentials
|
||||
and the connection and nothing else. Building a query and shaping its rows are
|
||||
ordinary Function nodes on either side, so a dashboard widget never learns which
|
||||
database answered it.
|
||||
|
||||
### Notification
|
||||
|
||||
**`ntfy`** — push an incoming value to a phone through [ntfy](https://ntfy.sh).
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `server` | `https://ntfy.sh` | |
|
||||
| `topic` | — | required |
|
||||
| `title` | — | |
|
||||
| `priority` | `default` | `min`, `low`, `default`, `high`, `urgent` |
|
||||
| `tags` | — | comma-separated ntfy tags |
|
||||
| `token` | — | for a protected topic; takes a secret reference |
|
||||
|
||||
This is a *flow* deciding something is worth saying. The engine reporting that
|
||||
it broke is [Alerts](../interface/operations.md#alerts), which is a different
|
||||
thing configured elsewhere.
|
||||
|
||||
## Timing
|
||||
|
||||
### Inject
|
||||
|
||||
**`inject`** — emit a value on request, on a timer, or when the flow starts.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `payload` | current time | what to emit |
|
||||
| `payloads` | `{}` | per-output-port payloads, keyed by port |
|
||||
| `interval` | `0` | emit every n seconds; 0 means never on its own |
|
||||
| `cron` | — | a five-field cron expression |
|
||||
| `at_start` | `false` | emit once when the flow starts |
|
||||
| `start_delay` | `1.0` | how long to wait before that first emission |
|
||||
|
||||
The most-placed trigger in a real installation — mostly as a button someone
|
||||
presses.
|
||||
|
||||
### Delay & schedule
|
||||
|
||||
**`delay`** — hold messages back, limit their rate, or emit on a schedule.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `delay` | `0` | seconds to hold each message |
|
||||
| `interval` | `0` | minimum seconds between forwards |
|
||||
| `mapping` | `{}` | input port → output port; paired in order when empty |
|
||||
| `cron` | — | five-field expression |
|
||||
|
||||
Order of operations: rate check → delay → forward. With a `cron` and no inputs
|
||||
it emits the current time on each tick; with inputs it emits the last value it
|
||||
received.
|
||||
|
||||
!!! note "Not in a batch flow"
|
||||
|
||||
A rate limit holds a value back for a timer to release, and a run has no
|
||||
timer — the value would be dropped rather than delayed. Submitting a batch
|
||||
flow with a rate-limited port is refused instead.
|
||||
|
||||
### Trigger
|
||||
|
||||
**`trigger`** — send one value now and another once things go quiet.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `first` | `true` | sent as soon as a value arrives |
|
||||
| `then` | `false` | sent when the wait expires; empty sends nothing |
|
||||
| `wait` | `60` | seconds of quiet before the second value |
|
||||
| `extend` | `true` | a value arriving during the wait starts it over |
|
||||
|
||||
The shape this exists for: *the door opened — turn the light on, and off again
|
||||
in two minutes unless it opens again.*
|
||||
|
||||
## Logic
|
||||
|
||||
### Switch
|
||||
|
||||
**`switch`** — send a value down one branch or another, by rule.
|
||||
|
||||
Each rule names an output port; a matching value leaves through that port.
|
||||
Comparisons: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `between`.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `rules` | `[]` | checked in order; each names the output it routes to |
|
||||
| `stop_at_first` | `true` | leave through the first matching rule only |
|
||||
| `otherwise` | — | output for a value that matched nothing |
|
||||
|
||||
Comparing a string to a number is a mistake in the rule, not a reason to take
|
||||
the flow down: the rule simply does not match.
|
||||
|
||||
### Change
|
||||
|
||||
**`change`** — scale, offset, round or map a value on its way past.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `scale` | `1.0` | multiply numbers by this |
|
||||
| `offset` | `0.0` | then add this |
|
||||
| `round_to` | — | decimal places |
|
||||
| `mapping` | `{}` | replace a value with another, looked up as text |
|
||||
| `default` | — | value when the lookup misses; empty passes it through |
|
||||
|
||||
### Filter unchanged
|
||||
|
||||
**`rbe`** — pass a value on only when it has actually changed.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `deadband` | `0.0` | ignore numeric changes smaller than this |
|
||||
| `deadband_percent` | `false` | read the deadband as a percentage |
|
||||
|
||||
The cheapest fix for a sensor that reports every second and changes every hour.
|
||||
|
||||
### Join
|
||||
|
||||
**`join`** — gather several inputs into one object or list.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `mode` | `object` | `object` or `array` |
|
||||
|
||||
## System
|
||||
|
||||
### Command
|
||||
|
||||
**`exec`** — run a command in the engine's container and read its output.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `command` | — | the command to run |
|
||||
| `append_payload` | `false` | add the incoming value as one argument |
|
||||
| `timeout` | `30` | seconds |
|
||||
| `fail_on_error` | `false` | treat a non-zero exit as a node failure rather than output |
|
||||
|
||||
Outputs the command's stdout, stderr and exit code.
|
||||
|
||||
!!! warning "Inside the container, not on the host"
|
||||
|
||||
A flow ported from something that read the host's journal, or poked a host
|
||||
script, needs either a mount or a small listener on the host side. This
|
||||
node cannot see the host.
|
||||
|
||||
### File
|
||||
|
||||
**`file`** — read a file into the flow, or write one out of it.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `path` | — | relative to the engine's files directory |
|
||||
| `mode` | `read` | `read`, `write` or `append` |
|
||||
| `format` | `text` | `text` or `json` |
|
||||
| `newline` | `true` | end each written record with a newline |
|
||||
|
||||
Confined to a directory the engine owns. A flow that could name any path would
|
||||
be a way to read the secrets store or overwrite a node's source.
|
||||
|
||||
## Numeric
|
||||
|
||||
### Perceptron
|
||||
|
||||
**`mlp`** — a small neural layer over its numeric inputs.
|
||||
|
||||
`output = weights @ inputs + biases`, with weights drawn from `seed` so a node
|
||||
reloads identically.
|
||||
|
||||
| Setting | Default | Notes |
|
||||
|---|---|---|
|
||||
| `seed` | `0` | |
|
||||
|
||||
Kept as a worked example of numeric logic rather than as a modelling tool. If
|
||||
you are training something, that is a [batch flow](../concepts/runs.md) and a
|
||||
Function node.
|
||||
|
||||
## Connectors
|
||||
|
||||
Anything else in the palette came from an installed **connector** package — a
|
||||
node type written against a published contract and discovered through the
|
||||
`fluksio.node_types` entry point group. The editor shows which package supplied
|
||||
it.
|
||||
|
||||
A connector declares its contract version, and one written for a version this
|
||||
engine does not speak is ignored rather than half-loaded. Installing or
|
||||
upgrading one takes effect on the next engine restart, because Python does not
|
||||
re-import a changed module and a rescan would promise more than it delivers.
|
||||
|
||||
## See also
|
||||
|
||||
- [Payload types](payload-types.md) — what a port may carry
|
||||
- [Writing node code](../code/nodes.md) — the Function node
|
||||
- [The flow editor](../interface/flow-editor.md) — placing and configuring them
|
||||
@@ -0,0 +1,129 @@
|
||||
# Payload types
|
||||
|
||||
Every port declares a `dtype`, and every value that passes through it is
|
||||
checked against that declaration.
|
||||
|
||||
This is not decoration. It is what lets the dashboard editor offer you only the
|
||||
messages a gauge can actually draw, what lets the canvas refuse a binding before
|
||||
anything runs, and what lets a downstream node know the shape of what it is
|
||||
getting before the flow starts.
|
||||
|
||||
Everything on the wire is JSON. That is what lets the same value pass through
|
||||
the state backend, the work queue and the worker protocol unchanged.
|
||||
|
||||
## The scalars
|
||||
|
||||
| `dtype` | Accepts |
|
||||
|---|---|
|
||||
| `float` | any number — `int` or `float`, but not `bool` |
|
||||
| `int` | a whole number, not `bool` |
|
||||
| `str` | a string |
|
||||
| `bool` | exactly `true` or `false` |
|
||||
|
||||
`bool` is an `int` subclass in Python and deliberately not a number here: a flag
|
||||
is not a measurement, and a switch bound to a temperature is a mistake worth
|
||||
catching.
|
||||
|
||||
## The structured ones
|
||||
|
||||
These are *declared shapes* rather than "some JSON", which is what makes a
|
||||
widget binding checkable.
|
||||
|
||||
### `record`
|
||||
|
||||
Flat named scalars.
|
||||
|
||||
```json
|
||||
{"title": "Boiler", "body": "Pressure low", "severity": "warning"}
|
||||
```
|
||||
|
||||
Nesting is deliberately out: a record that can contain a record is a schema
|
||||
language, and the shape stops being readable from the declaration alone.
|
||||
|
||||
Read by the **Notification** widget. It is also what an alert channel of kind
|
||||
*dashboard* writes.
|
||||
|
||||
### `list`
|
||||
|
||||
Ordered items of one declared shape. The port also declares `item`:
|
||||
|
||||
| `item` | Meaning |
|
||||
|---|---|
|
||||
| unset | `record` — what the agenda and forecast widgets read |
|
||||
| `float`, `int`, `str`, `bool` | a list of scalars |
|
||||
| `json` | anything |
|
||||
|
||||
A list of lists, or a list of series, is refused. One declared level is the
|
||||
point.
|
||||
|
||||
### `series`
|
||||
|
||||
Labelled lines of `(timestamp, value)` pairs — what a chart draws.
|
||||
|
||||
```json
|
||||
{
|
||||
"lines": [
|
||||
{"label": "living", "points": [[1717000000, 21.4], [1717000060, 21.5]]}
|
||||
],
|
||||
"range": "-24h"
|
||||
}
|
||||
```
|
||||
|
||||
Keys beside `lines` are carried through untouched, which is how a querying
|
||||
chart puts the window and resolution it asked for on the request and reads them
|
||||
back off the answer. That is what stops an answer to a *different* question
|
||||
from overwriting the picture.
|
||||
|
||||
`GET /runs/series/compare` answers in this shape, which is why comparing three
|
||||
training curves is a widget binding rather than a screen of its own.
|
||||
|
||||
### `artifact`
|
||||
|
||||
A reference to stored bytes.
|
||||
|
||||
```json
|
||||
{"digest": "sha256:…", "size": 4194304, "media_type": "application/octet-stream", "name": "weights.pt"}
|
||||
```
|
||||
|
||||
Binary payloads — tensors, checkpoints, images — never travel as a message. The
|
||||
bytes go to a content-addressed store and the message carries this. A
|
||||
thirty-megabyte checkpoint never sits in the state backend, and the reference
|
||||
stays valid wherever the store is reachable from, including on another machine.
|
||||
|
||||
Node code produces one with `fluksio.save_artifact` and opens one with
|
||||
`fluksio.load_artifact`. See [Writing node code](../code/nodes.md#bytes-artifacts).
|
||||
|
||||
### `json`
|
||||
|
||||
Anything JSON-serializable. The escape hatch, and the right answer when a
|
||||
payload genuinely has no fixed shape.
|
||||
|
||||
Reach for it last. A `json` port tells the canvas, the widget picker and the
|
||||
next author nothing.
|
||||
|
||||
## What a widget will bind to
|
||||
|
||||
| Widget | Accepts |
|
||||
|---|---|
|
||||
| Gauge, Chart, Slider, Bar | `float`, `int` |
|
||||
| Switch | `bool` |
|
||||
| Agenda, Forecast | `list` |
|
||||
| Notification | `record` |
|
||||
| Value | anything |
|
||||
| Icon | weather strings, booleans and numbers alike |
|
||||
| Clock, Text | nothing — they bind to no message |
|
||||
|
||||
Enforced on the server as well as in the editor.
|
||||
|
||||
## Type failures
|
||||
|
||||
A value that does not match its port's declaration raises on the node that
|
||||
published it, naming the port and what arrived. It does not get published, and
|
||||
it does not reach anything downstream — a wrong value stopping at its source is
|
||||
much easier to diagnose than one propagating.
|
||||
|
||||
## See also
|
||||
|
||||
- [Flows, nodes and messages](../concepts/flows.md)
|
||||
- [Node types](node-types.md)
|
||||
- [Dashboards and panels](../interface/dashboards.md)
|
||||
Reference in New Issue
Block a user