linuxrouter/docker/routlin-dash/TYPES.md
2026-06-06 14:55:29 -04:00

18 KiB

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:

{ "client_requirement": "client_is_administrator+", "type": "card", "items": [] }

Layout / structure

h1

{ "type": "h1", "text": "Page Title" }

Renders as <h1>.


hr

{ "type": "hr" }

Full-width horizontal rule.


p

{
  "type": "p",
  "text": "Introductory text.",
  "link?": { "text": "Click here", "action": "/some-path" }
}

Paragraph with an optional inline link appended after the text.


spacer

{ "type": "spacer" }

Blank vertical space (<div class="spacer">).


header_page_title

{
  "type": "header_page_title",
  "items": [ ... ]
}

Wraps children in the page header div. Typically contains h1 and toolbar buttons.


section

{
  "type": "section",
  "items": [ ... ]
}

Generic section wrapper (<div class="section">).


auth_wrapper

{
  "type": "auth_wrapper",
  "items": [ ... ]
}

Full-page centering wrapper for login/auth pages.


auth_card

{
  "type": "auth_card",
  "items": [ ... ]
}

Contained card used on auth pages.


card

{
  "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

{
  "type": "grid",
  "rows": [
    { "cells": [ { "type": "grid_label", ... }, { "type": "grid_value", ... } ] }
  ]
}

Two-column info grid. Each row contains cells rendered inline.


grid_label

{ "type": "grid_label", "text": "Label" }

Label cell in an info grid row.


grid_value

{ "type": "grid_value", "text": "%TOKEN%" }

Value cell in an info grid row. Token substitution applies.


raw_html

{ "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

{
  "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

{ "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

{
  "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

{
  "type": "stat_card_grid",
  "items": [ ... ]
}

Responsive grid container for stat cards.


stat_card

Three variants:

Read-only:

{
  "type": "stat_card",
  "label": "Active Sessions",
  "value": "%VPN_SESSION_COUNT%",
  "sub?": "Secondary text",
  "variant?": "wide"
}

Inline editable:

{
  "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):

{
  "type": "stat_card",
  "label": "VPN Subnet",
  "value": "%VPN_SUBNET%",
  "reveal_card_id": "edit-vpn-subnet-card"
}

Info / status

info_bar

{
  "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

{
  "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

{
  "type": "pre_block",
  "text": "%LOG_OUTPUT%",
  "scroll_to_bottom?": true
}

<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.


Forms

form

{
  "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

{ "type": "hidden", "name": "provider", "value": "%PROVIDER%" }

Hidden input. Token substitution applies.


field

The general form field. Behavior is driven by input_type.

Common fields:

{
  "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

{
  "type": "field_row",
  "cols?": 2,
  "items": [ ... ]
}

Horizontal row of fields in cols equal columns (default 2). Children are typically field items.


subnet_row

{
  "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

{
  "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

{
  "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

{
  "type": "readonly_select",
  "label?": "Gateway",
  "name?": "gateway",
  "hint?": "Helper text shown below the select."
}

Disabled <select> placeholder shown while dependent data is loading. JS replaces it with real options once identities are available.


overridable_textarea

{
  "type": "overridable_textarea",
  "label": "Custom Rules",
  "name": "custom_rules",
  "override_name?": "custom_rules_override",
  "validate?": "ip_in_subnet",
  "hint?": "Helper text shown below the textarea."
}

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

{
  "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

{
  "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

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 |:

{ "type": "field", "name": "nat_ip", "validate": "VALIDATION_IPV4_FORMAT|VALIDATION_UNRESTRICTED" }

All three contexts use the same flag system:

  • 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.

number input_type defaults to VALIDATION_RANGE_INT when no validate is specified.

VALIDATION_* flags

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.

Table

table

{
  "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

{
  "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

{
  "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.

{
  "method": "js_edit",
  "text": "Edit",
  "target": "edit-vlan-form"
}

"inline_edit" converts the table row into editable inputs in place. Fields are defined inline:

{
  "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.

{
  "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.

{ "type": "nav_action", "label": "Logout", "action": "logout" }

nav_menu

Dropdown menu.

{
  "type": "nav_menu",
  "label": "Configure",
  "items": [ ... ]
}

If label is "%MENU_LABEL%", it renders as "Configure" for administrators and above, or "View" for viewers.