feat: composer improvements

This commit is contained in:
2026-04-05 09:53:01 +02:00
parent 7838760ca4
commit e1db06104e
17 changed files with 1301 additions and 332 deletions

151
CLAUDE.md
View File

@@ -205,27 +205,154 @@ form "name"
```
### Dynamic Features
```
cache 0 # never cache (re-execute)
source cpu : shell "cat /proc/loadavg" # live data at render time
source config : json "/path/config.json" # JSON file read
source ts : python "datetime.now().isoformat()" # Python expression
let name = "Relay Alpha" # variable assignment
Any page using `source`, `if`, `for`, `on_submit`, or `state` becomes **dynamic**: it compiles to an executable Python script instead of static Micron. NomadNet runs the script on each request and serves its stdout.
#### Variables
```
let name = "Relay Alpha" # string assignment
let threshold = 75 # numeric
let tags = "alpha","beta","gamma" # comma-separated → list
```
Variables are substituted with `$name` in text, labels, and other content. They work in both static and dynamic pages.
#### Data Sources
```
source var_name : type "command" [timeout=N]
```
Sources fetch data **at render time** and bind results to variables:
| Type | Description | Example |
|----------|--------------------------------------|----------------------------------------------------------|
| `shell` | Run shell command, capture stdout | `source cpu : shell "cat /proc/loadavg"` |
| `file` | Read file contents as string | `source motd : file "/etc/motd"` |
| `json` | Read + parse JSON file → dict/list | `source config : json "/etc/config.json"` |
| `python` | Evaluate Python expression | `source ts : python "datetime.now().strftime('%H:%M')"` |
| `http` | HTTP request, auto-parses JSON | `source data : http "https://api.example.com/data"` |
| `sqlite` | SQLite query → list of dicts | `source users : sqlite "/path/db" "SELECT * FROM users"` |
| `env` | Read environment variable | `source key : env "API_KEY"` |
| `param` | Read URL parameter from link | `source hash : param "hash"` |
| `rns` | Query Reticulum via `rnstatus` | `source peers : rns "peers"` |
**Shell** commands have a default 5-second timeout (override with `timeout=N`).
**Python** expressions have access to: `datetime` (the class, so `datetime.now()` works), `timedelta`, `secrets`, `os`, `json`. Expressions are evaluated via `eval()` — single expressions only, not statements.
```
# Python source examples
source timestamp : python "datetime.now().strftime('%H:%M:%S')"
source rand_id : python "secrets.token_hex(4)"
source cpu_sim : python "secrets.randbelow(60) + 20"
source uptime : python "str(timedelta(seconds=12345))"
source hostname : python "os.uname().nodename"
```
**HTTP** requests return parsed JSON (dict/list) or raw string. Default timeout 10s.
```
# GET request — JSON auto-parsed into dict
source todo : http "https://api.example.com/todos/1"
text "Title: $todo.title"
# POST with JSON body
source result : http "https://api.example.com/search" method=POST body='{"q":"relay"}'
# Custom headers (semicolon-separated)
source data : http "https://api.example.com/data" headers='Authorization: Bearer tok123'
# Use $var references in URL, headers, and body — resolved at runtime
source token : env "API_TOKEN"
source data : http "https://api.example.com/data" headers='Authorization: Bearer $token'
```
**Env** reads server-side environment variables. Use this for secrets — tokens never appear in `.uf` source or compiled scripts.
```
source api_key : env "API_KEY"
source db_pass : env "DB_PASSWORD"
```
**SQLite** queries return a list of dicts (or a single dict for one row). Uses Python stdlib `sqlite3`.
```
# Query returns list of dicts with column names as keys
source nodes : sqlite "/data/network.db" "SELECT name, status, hops FROM nodes"
# Iterate results
for node in $nodes
label "$node.name" "$node.status ($node.hops hops)"
# Single row queries return a dict directly
source config : sqlite "/data/app.db" "SELECT value FROM config WHERE key='theme'"
text "Theme: $config.value"
```
#### Conditionals
```
if $cpu > 90
text "ALERT: CPU critical"
elif $cpu > 75
text "Warning: elevated"
else
text "All clear"
```
Conditions are Python expressions. `$var` references resolve to the variable's value. Supports `>`, `<`, `>=`, `<=`, `==`, `!=`, `&&` (and), `||` (or).
#### Loops
```
for peer in $peers
status "$peer.name" $peer.state
on_submit "search"
source results : shell "search.py '$query'"
text "$results"
state "counter" "/tmp/counter.json" # persistent JSON store
```
Iterates over lists (from JSON sources), dicts (wrapped as single-item list), or newline-delimited strings (from shell output). Access nested fields with `$item.field`.
#### Cache Control
```
cache 0 # never cache (re-execute every request)
cache 300 # cache for 5 minutes
```
Emits the `#!c=N` header that NomadNet uses to control page caching.
#### Form Submission Handling
```
on_submit "form_name"
# Runs when the named form is submitted
# Form field values are available as $field_name
source results : shell "search.py '$query'"
text "Found: $results"
```
Field values are read from `FIELD_*` environment variables set by NomadNet.
#### Persistent State
```
state "counter" "/tmp/counter.json" # load JSON into $counter
```
Loads a JSON file into a variable. Use `_save_state(path, data)` in the generated script to persist changes.
#### Using Variables in Content
```
text "Hello, $name" # inline substitution
label "CPU" "$cpu_pct%" # in labels
gauge "CPU" $cpu_pct 100 28 warn=75 crit=90 # as gauge values
status "$peer" $state # in status indicators
link "View $name" "/page/detail.mu" # in links
```
#### Generated Script Runtime
The compiled script includes these helpers, available in `on_submit` and source blocks:
| Helper | Description |
|-------------------------------------|-----------------------------------------------|
| `_shell(cmd, timeout=5)` | Execute shell command, return stdout |
| `_read_file(path)` | Read file contents |
| `_read_json(path)` | Read + parse JSON file |
| `_http(url, method, body, headers)` | HTTP request, auto-parse JSON response |
| `_sqlite(db_path, query)` | SQLite query → list of dicts (or single dict) |
| `_get_field(name, default)` | Read submitted form field |
| `_get_param(name, default)` | Read URL parameter |
| `_load_state(path)` | Load state from JSON file |
| `_save_state(path, data)` | Save state to JSON file |
| `_iter(val)` | Make a value iterable (list/dict/string) |
### Components
```