linuxrouter/docker/routlin-dash/TYPES.md

687 lines
18 KiB
Markdown
Raw Normal View History

2026-05-28 22:50:00 -04:00
# Content Item Types
All `content.json` files use an `items` array of typed objects. Every object has a `type` field; all other fields depend on the type. Optional fields are marked with `?`.
Token substitution is available in most string fields: any `%TOKEN%` placeholder is replaced with the corresponding value. Where noted, a string that resolves to a JSON array or object is automatically parsed back into the appropriate structure.
---
## Access control
Any item (and any table column or row action) can carry a `client_requirement` field. Items whose requirement is not satisfied are omitted entirely from the rendered output.
Roles are ranked from lowest to highest:
| Rank | Role | Description |
|------|------|-------------|
| 3 | `manager` | Superuser; can manage accounts and administrators |
| 2 | `administrator` | Full configuration access |
| 1 | `viewer` | Read-only authenticated user |
| 0 | `nothing` | Unauthenticated (no session) |
The value is a role name with a suffix that controls the comparison:
| Suffix | Meaning |
|--------|---------|
| `+` | Client rank must be greater than or equal to the named role |
| `=` | Client rank must equal the named role exactly |
| `-` | Client rank must be less than or equal to the named role |
Possible values for `client_requirement` field:
| Value | Meaning |
|----------|-------------|
| `client_is_manager=` | Client must be manager |
| `client_is_manager-` | Client must be manager or lower |
| `client_is_administrator+` | Client must be administrator or higher |
| `client_is_administrator=` | Client must be administrator |
| `client_is_administrator-` | Client must be administrator or lower |
| `client_is_viewer+` | Client must be viewer or higher |
| `client_is_viewer=` | Client must be viewer |
| `client_is_viewer-` | Client must be viewer or lower |
| `client_is_nothing+` | Client may be anybody (logged in or not logged in) |
| `client_is_nothing=` | Client must be nothing (not logged in) |
Example:
```json
{ "client_requirement": "client_is_administrator+", "type": "card", "items": [] }
```
---
## Layout / structure
### `h1`
```json
{ "type": "h1", "text": "Page Title" }
```
Renders as `<h1>`.
---
### `hr`
```json
{ "type": "hr" }
```
Full-width horizontal rule.
---
### `p`
```json
{
"type": "p",
"text": "Introductory text.",
"link?": { "text": "Click here", "action": "/some-path" }
}
```
Paragraph with an optional inline link appended after the text.
---
### `spacer`
```json
{ "type": "spacer" }
```
Blank vertical space (`<div class="spacer">`).
---
### `header_page_title`
```json
{
"type": "header_page_title",
"items": [ ... ]
}
```
Wraps children in the page header div. Typically contains `h1` and toolbar buttons.
---
### `section`
```json
{
"type": "section",
"items": [ ... ]
}
```
Generic section wrapper (`<div class="section">`).
---
### `auth_wrapper`
```json
{
"type": "auth_wrapper",
"items": [ ... ]
}
```
Full-page centering wrapper for login/auth pages.
---
### `auth_card`
```json
{
"type": "auth_card",
"items": [ ... ]
}
```
Contained card used on auth pages.
---
### `card`
```json
{
"type": "card",
"label?": "Card Title",
"id?": "my-card-id",
"hidden?": true,
"items": [ ... ]
}
```
General-purpose card with an optional header label. Setting `hidden: true` renders the card with the `hidden` attribute, which is used for `js_edit` reveal targets. The `id` attribute wires it up as a `js_edit` target.
---
### `grid`
```json
{
"type": "grid",
"rows": [
{ "cells": [ { "type": "grid_label", ... }, { "type": "grid_value", ... } ] }
]
}
```
Two-column info grid. Each row contains `cells` rendered inline.
---
### `grid_label`
```json
{ "type": "grid_label", "text": "Label" }
```
Label cell in an info grid row.
---
### `grid_value`
```json
{ "type": "grid_value", "text": "%TOKEN%" }
```
Value cell in an info grid row. Token substitution applies.
---
### `raw_html`
```json
{ "type": "raw_html", "html": "<strong>Some markup</strong>" }
```
Injects raw HTML without escaping. Token substitution applies. Use sparingly.
---
## Buttons
All button types share these optional fields:
| Field | Meaning |
|-------|---------|
| `text` | Button label |
| `class?` | Extra CSS classes appended to the default |
| `disabled?` | Token expression; disables button if truthy (not `"false"` / `"0"`) |
### `button_primary` / `button_secondary` / `button_danger` / `button_ghost`
```json
{
"type": "button_primary",
"text": "Save",
"action?": "/some/path",
"method?": "post",
"formaction?": "/alternate/path",
"disabled?": "%SOME_TOKEN%"
}
```
- No `action`: renders `<button type="submit">` inside an existing form.
- `action` + `method: "post"`: wraps in a standalone `<form method="post">`.
- `action` only (no method): renders `<a href="...">` styled as a button.
- `formaction`: overrides the form action on this submit button (for multi-target forms).
### `button_cancel`
```json
{ "type": "button_cancel", "text?": "Cancel", "class?": "extra-cls" }
```
Always rendered as a disabled button (`btn-secondary`). JS enables it when the form becomes dirty.
### `button_row`
```json
{
"type": "button_row",
"justify?": "flex-end",
"items": [ ... ]
}
```
Flex container for buttons. Children are typically button types. `justify` sets `justify-content`.
---
## Stat cards
### `stat_card_grid`
```json
{
"type": "stat_card_grid",
"items": [ ... ]
}
```
Responsive grid container for stat cards.
---
### `stat_card`
Three variants:
**Read-only:**
```json
{
"type": "stat_card",
"label": "Active Sessions",
"value": "%VPN_SESSION_COUNT%",
"sub?": "Secondary text",
"variant?": "wide"
}
```
**Inline editable:**
```json
{
"type": "stat_card",
"label": "Max Log Size",
"value": "%GENERAL_LOG_MAX_KB%",
"edit_action": "/action/update_log_max_kb",
"edit_field": "log_max_kb",
"edit_input_type?": "number",
"edit_value?": "%GENERAL_LOG_MAX_KB%",
"edit_suffix?": "KB",
"edit_min?": "64"
}
```
**Reveal (opens a card):**
```json
{
"type": "stat_card",
"label": "VPN Subnet",
"value": "%VPN_SUBNET%",
"reveal_card_id": "edit-vpn-subnet-card"
}
```
---
## Info / status
### `info_bar`
```json
{
"type": "info_bar",
"variant": "info",
"text": "Some note about this section."
}
```
Renders inline (not as a full-width layout bar). Variants: `info`, `warning`, `danger`.
---
### `field_status`
```json
{
"type": "field_status",
"label": "WAN Interface",
"value": "%GENERAL_WAN_STATUS%"
}
```
Form-group layout with a badge showing interface state (`UP`/`DOWN`/`INVALID`/other).
---
### `pre_block`
```json
{
"type": "pre_block",
"text": "%LOG_OUTPUT%",
"scroll_to_bottom?": true
}
```
2026-06-06 14:55:29 -04:00
`<pre>` block for log output. Lines do not wrap; content scrolls horizontally when lines exceed the container width. Setting `scroll_to_bottom: true` adds a `data-scroll-bottom` attribute that JS uses to auto-scroll to the bottom on load and on update.
2026-05-28 22:50:00 -04:00
---
## Forms
### `form`
```json
{
"type": "form",
"action": "/action/save_settings",
"method?": "post",
"items": [ ... ]
}
```
HTML form. Automatically injects:
- `config_hash` hidden input (CSRF / stale-config detection)
- `original_values` hidden input (JSON map of field names to original values, used for change detection)
Also generates a validation `<script>` block if any child fields have `validate` or `number` input type.
---
### `hidden`
```json
{ "type": "hidden", "name": "provider", "value": "%PROVIDER%" }
```
Hidden input. Token substitution applies.
---
### `field`
The general form field. Behavior is driven by `input_type`.
**Common fields:**
```json
{
"type": "field",
"input_type": "text",
"label": "Hostname",
"name": "hostname",
"value?": "%HOSTNAME%",
"placeholder?": "e.g. myhost.example.com",
"hint?": "Helper text shown below the input.",
"readonly?": true,
"validate?": "hostname",
"client_requirement?": "client_is_administrator+"
}
```
**`input_type` values:**
| Value | Renders | Extra fields |
|-------|---------|--------------|
| `text` *(default)* | `<input type="text">` | `validate?`, `depends?` |
| `password` | `<input type="password">` | `validate?`, `depends?` |
| `number` | `<input type="number">` | `min?`, `max?`, `validate?` (default `positive_int`), `depends?`, `layout?` (`"inline"`) |
| `checkbox` | Toggle checkbox | `checkbox_label?` (inline label next to box) |
| `checkbox_group` | Multiple checkboxes | `options`: JSON array of `{value, label}` |
| `select` | `<select>` | `options`: JSON array of `{value, label}` or token string; `validate?`, `depends?` |
| `textarea` | `<textarea>` | `rows?`, `validate?` |
| `interface_picker` | Table picker for network interfaces | `data?`: token resolving to JSON array of interface rows; `value?` |
`depends` is a list of field names whose values are sent as `data-depends` for JS-driven conditional validation.
---
### `field_row`
```json
{
"type": "field_row",
"cols?": 2,
"items": [ ... ]
}
```
Horizontal row of fields in `cols` equal columns (default 2). Children are typically `field` items.
---
### `subnet_row`
```json
{
"type": "subnet_row",
"label?": "Subnet",
"subnet_name?": "subnet",
"prefix_name?": "subnet_mask",
"subnet_value?": "%VLAN_SUBNET%",
"prefix_value?": "24",
"subnet_placeholder?": "e.g. 192.168.1.0"
}
```
Combined subnet address and prefix length inputs. Displays the dotted mask equivalent (e.g. `/24` becomes `255.255.255.0`) as a hint below. Auto-validates via JS.
---
### `select`
```json
{
"type": "select",
"name": "filter_field",
"options": "<option value=\"a\">A</option>",
"filter_col?": "status"
}
```
Bare `<select>` outside a form-group. `options` is raw HTML. `filter_col` wires it to filter a sibling table column via JS.
---
### `record_editor`
```json
{
"type": "record_editor",
"label": "Hostnames",
"name": "hostnames",
"empty_message?": "No hostnames added.",
"fields": [
{
"label": "Hostname",
"name": "hostname",
"placeholder?": "e.g. myhost.example.com",
"required?": true,
"validate?": "hostname",
"valtype?": "string",
"attrs?": { "data-custom": "value" }
}
]
}
```
JS-driven table where the user can add, edit, and remove rows. Submits as a JSON array in the named hidden input. Each entry in `fields` gets its own column and a corresponding input in the add/edit form.
---
### `readonly_select`
```json
{
"type": "readonly_select",
"label?": "Gateway",
2026-05-29 22:26:30 -04:00
"name?": "gateway",
"hint?": "Helper text shown below the select."
2026-05-28 22:50:00 -04:00
}
```
Disabled `<select>` placeholder shown while dependent data is loading. JS replaces it with real options once identities are available.
---
### `overridable_textarea`
```json
{
"type": "overridable_textarea",
"label": "Custom Rules",
"name": "custom_rules",
"override_name?": "custom_rules_override",
2026-05-29 22:26:30 -04:00
"validate?": "ip_in_subnet",
"hint?": "Helper text shown below the textarea."
2026-05-28 22:50:00 -04:00
}
```
Read-only textarea with an "Override" checkbox. Checking the box enables editing. `override_name` is the checkbox's `name` attribute and defaults to `{name}_override`.
---
### `editable_list`
```json
{
"type": "editable_list",
"name": "blocklist_urls",
"item_placeholder?": "https://...",
"add_label?": "Add URL",
"hint?": "One URL per entry.",
"items?": "%BLOCKLIST_URLS_JSON%"
}
```
Simple list where the user can add and remove text items. Submits as a JSON array.
---
### `credential_fields`
```json
{
"type": "credential_fields",
"provider_select?": "provider"
}
```
Group of hidden credential sub-groups (API token vs. No-IP username/password). JS shows the correct group based on the named provider `<select>`. `provider_select` is the `name` of that select and defaults to `"provider"`.
---
## Validation
2026-06-06 14:55:29 -04:00
The `validate` field attaches one or more `VALIDATION_*` flags to an input. Validators run live as the user types, show inline hint messages, and keep the form's submit button disabled until all validated fields pass. Flags are combined with `|`:
2026-05-28 22:50:00 -04:00
2026-06-06 14:55:29 -04:00
```json
{ "type": "field", "name": "nat_ip", "validate": "VALIDATION_IPV4_FORMAT|VALIDATION_UNRESTRICTED" }
```
2026-05-28 22:50:00 -04:00
2026-06-06 14:55:29 -04:00
All three contexts use the same flag system:
2026-05-28 22:50:00 -04:00
2026-06-06 14:55:29 -04:00
- `field`, `editable_list`: sets `data-validate` (bitmask integer) on the input. Validation fires on every keystroke.
- `overridable_textarea`: sets `data-validate-lines`. When the override checkbox is checked, each non-empty line is validated individually.
- `record_editor` fields: `validate` (or its legacy alias `valtype`) sets `data-validate` on the per-field input.
2026-05-28 22:50:00 -04:00
2026-06-06 14:55:29 -04:00
`number` input_type defaults to `VALIDATION_RANGE_INT` when no `validate` is specified.
2026-05-28 22:50:00 -04:00
2026-06-06 14:55:29 -04:00
### `VALIDATION_*` flags
2026-05-28 22:50:00 -04:00
2026-06-06 14:55:29 -04:00
| Flag | Accepts |
|------|---------|
| `VALIDATION_IPV4_FORMAT` | IPv4 address. Four dot-separated octets, each 0-255. |
| `VALIDATION_IPV6_FORMAT` | IPv6 address. |
| `VALIDATION_SUBNET` | IPv4 subnet address. All host bits must be zero for the prefix length implied by sibling inputs. |
| `VALIDATION_ADDRESS` | IPv4 host address. Must fall within the subnet implied by sibling inputs and must not be the network or broadcast address. |
| `VALIDATION_MAC` | MAC address. Six colon-separated two-digit hex groups. |
| `VALIDATION_URL` | HTTP or HTTPS URL. Must begin with `http://` or `https://` and have a valid hostname. |
| `VALIDATION_PORT` | Port number. Integer 1-65535. |
| `VALIDATION_DASH_NAME` | Lowercase letters, digits, and hyphens. No leading, trailing, or consecutive hyphens. |
| `VALIDATION_NETWORK_NAME` | Letters, digits, hyphens, and underscores. No leading, trailing, or consecutive special characters. |
| `VALIDATION_DOMAIN_NAME` | Domain name. Labels of letters, digits, and hyphens separated by dots. No label may start or end with a hyphen. |
| `VALIDATION_TIME24H` | 24-hour time. Format `HH:MM`. Hours 00-23, minutes 00-59. |
| `VALIDATION_RANGE_INT` | Integer. Respects the field's `min` and `max` bounds when present. Default for `number` input_type. |
| `VALIDATION_IPV4_CIDR` | Strict CIDR. A prefix is always required (`192.168.1.0/24`). Host bits must be zero. |
| `VALIDATION_IPV4_CIDRFLEX` | Flexible CIDR. Accepts a bare IPv4 address (yellow/incomplete when last octet is 0) or `IP/prefix`. |
| `VALIDATION_UNRESTRICTED` | IPv4 address must not fall within any restricted VLAN subnet. Factory automatically injects the current restricted subnets into `data-existing-ids` on the input. |
| `VALIDATION_IP_OR_DOMAIN_NAME` | IPv4 address or hostname/domain name. |
2026-05-28 22:50:00 -04:00
---
## Table
### `table`
```json
{
"type": "table",
"datasource": "config:banned_ips",
"empty_message?": "No banned IPs.",
"columns": [ ... ],
"row_actions?": [ ... ],
"toolbar?": { "client_requirement?": "...", "items": [ ... ] }
}
```
**Datasource** prefixes:
| Prefix | Source |
|--------|--------|
| `config:<name>` | Reads from `config.json`. Available names: `vlans`, `banned_ips`, `host_overrides`, `port_forwardings`, `ddns_providers`, `upstream_dns_servers`, `accounts` |
| `live:<name>` | Live system data. Available names: `dhcp_leases`, `vpn_sessions` |
**Toolbar** renders content items (typically buttons) above the table, gated by `client_requirement`.
#### Column definition
```json
{
"label": "Enabled",
"field": "enabled",
"class?": "col-center",
"render?": "badge_enabled_disabled",
"render_options?": { "title_true": "...", "title_false": "..." },
"toggle_action?": "/action/toggle_vlan",
"client_requirement?": "client_is_administrator+"
}
```
**`render` values:**
| Value | Output |
|-------|--------|
| *(none)* | Escaped plain text |
| `badge_enabled_disabled` | Green `Enabled` / red `Disabled` badge |
| `badge_yes_no` | Green `Yes` / red `No` badge. `render_options.title_true` and `title_false` add tooltips |
| `badge_recording_on_off` | Green `Recording On` / red `Recording Off` badge |
| `badge_toggle` | Badge that submits a POST to `toggle_action` when clicked (if allowed by `client_requirement`) |
| `badge_active_inactive` | `active` gets a green badge, `pending` gets yellow, anything else gets grey |
| `raw_html` | Value injected as raw HTML (no escaping) |
| `tag_list` | JSON array of tag objects `{n, d?, short?, mini?}` rendered as pill tags |
| `interface_status` | `UP` gets a green badge, `DOWN` gets yellow, `INVALID` gets red |
#### Row action definition
```json
{
"method": "post",
"text": "Delete",
"action": "/action/delete_ban",
"class?": "btn-danger btn-sm",
"client_requirement?": "client_is_administrator+",
"disable_if?": { "field": "status", "value": "locked" }
}
```
**`method` values:**
**`"post"`** renders a form with `row_index` and `config_hash` hidden inputs. `disable_if` can disable the button when `row[field] == value`.
**`"js_edit"`** opens a reveal card by ID. Row data is passed as `data-row` JSON. The card element (a `card` item with matching `id`) is populated and shown by JS.
```json
{
"method": "js_edit",
"text": "Edit",
"target": "edit-vlan-form"
}
```
**`"inline_edit"`** converts the table row into editable inputs in place. Fields are defined inline:
```json
{
"method": "inline_edit",
"text": "Edit",
"action": "/action/save_host_override",
"fields": [
{
"label": "Hostname",
"name": "hostname",
"input_type": "text",
"validate?": "hostname"
},
{
"label": "Provider",
"name": "provider",
"input_type": "select",
"options": [
{ "value": "noip", "label": "No-IP" },
{ "value": "other", "label": "Other (API Token)" }
]
}
]
}
```
Non-standard `input_type` values in inline_edit fields (anything not in `text`, `password`, `number`, `checkbox`, `select`, `textarea`) require a **table worker** registered in JS. The factory emits a `<script>` block via `registerTableWorker(id, impl)` automatically when a non-standard type is detected. Currently the only non-standard inline type is:
| `input_type` | Page | What the worker renders |
|---|---|---|
| `credentials` | DDNS Accounts | Username/password or API token based on the row's `provider` field |
---
## Navigation (navbar.json only)
These types are used only in `app/navbar.json`, not in page `content.json` files.
### `nav_item`
A link or action in the navbar.
```json
{
"type": "nav_item",
"label": "Dashboard",
"map_to": "overview",
"client_requirement?": "client_is_viewer+"
}
```
### `nav_action`
Same as `nav_item` but uses an `action` field (POST) instead of a link.
```json
{ "type": "nav_action", "label": "Logout", "action": "logout" }
```
### `nav_menu`
Dropdown menu.
```json
{
"type": "nav_menu",
"label": "Configure",
"items": [ ... ]
}
```
If `label` is `"%MENU_LABEL%"`, it renders as `"Configure"` for administrators and above, or `"View"` for viewers.