692 lines
18 KiB
Markdown
692 lines
18 KiB
Markdown
|
|
# 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
|
||
|
|
}
|
||
|
|
```
|
||
|
|
`<pre>` block for log output. Setting `scroll_to_bottom: true` adds a `data-scroll-bottom` attribute that JS uses to auto-scroll.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 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",
|
||
|
|
"name?": "gateway"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
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",
|
||
|
|
"validate?": "ip_in_subnet"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
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
|
||
|
|
|
||
|
|
The `validate` field attaches a named validator 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.
|
||
|
|
|
||
|
|
Three mechanisms exist depending on context:
|
||
|
|
|
||
|
|
- `field`, `editable_list`: sets `data-validate` on the input. The named classifier runs on every keystroke and marks the field valid, incomplete (warning), or invalid (error).
|
||
|
|
- `overridable_textarea`: sets `data-validate-lines` on the textarea. When the override checkbox is checked, each non-empty line is validated as an IP address. If subnet context is available (from sibling subnet/mask inputs), each line is also checked against that subnet.
|
||
|
|
- `record_editor` fields: use `valtype` instead of `validate`. The field is wired to sibling subnet/mask inputs via `data-dep-subnet` and `data-dep-mask` attributes.
|
||
|
|
|
||
|
|
### `validate` values (for `field` and `editable_list`)
|
||
|
|
|
||
|
|
| Value | Accepts |
|
||
|
|
|-------|---------|
|
||
|
|
| `ipv4` | IPv4 address. Four dot-separated octets, each 0-255. |
|
||
|
|
| `mac` | MAC address. Six colon-separated two-digit hex groups. |
|
||
|
|
| `url` | HTTP or HTTPS URL. Must begin with `http://` or `https://`, have a valid hostname, and an optional port in range 1-65535. |
|
||
|
|
| `port` | Port number. Integer 1-65535, digits only. |
|
||
|
|
| `ipv4cidr` | IPv4 address or CIDR notation. Accepts a bare IP or `IP/prefix` where prefix is 0-32. |
|
||
|
|
| `endpoint` | Network endpoint. Accepts an IPv4 address, IPv6 address, or hostname. Hostnames may include a `:port` suffix. |
|
||
|
|
| `dashname` | Dash-delimited identifier. Lowercase letters, digits, and hyphens. No leading, trailing, or consecutive hyphens. |
|
||
|
|
| `domainname` | Domain name. Letters, digits, hyphens, and dots. Each label must not start or end with a hyphen. |
|
||
|
|
| `networkname` | Network identifier. Letters, digits, hyphens, and underscores. No leading, trailing, or consecutive special characters. |
|
||
|
|
| `time_24h` | 24-hour time. Format `HH:MM`. Hours 00-23, minutes 00-59. |
|
||
|
|
| `positive_int` | Positive integer. Digits only. Respects the field's `min` and `max` bounds when present. |
|
||
|
|
| `vlan_id` | VLAN ID. Integer 1-4094. Also checks uniqueness against existing IDs read from the input's `data-existing-ids` attribute. |
|
||
|
|
|
||
|
|
### `validate` value (for `overridable_textarea`)
|
||
|
|
|
||
|
|
| Value | Behavior |
|
||
|
|
|-------|---------|
|
||
|
|
| `ip_in_subnet` | Each non-empty line must be a valid IPv4 address. When the VLAN subnet and mask are both filled in, also verifies each address falls within that subnet. |
|
||
|
|
|
||
|
|
### `valtype` values (for `record_editor` fields)
|
||
|
|
|
||
|
|
`valtype` wires subnet-aware real-time validation using sibling inputs pointed to by `data-dep-subnet` and `data-dep-mask` attributes on the field.
|
||
|
|
|
||
|
|
| Value | Validates |
|
||
|
|
|-------|---------|
|
||
|
|
| `address` | IPv4 host address. Must be a valid IP within the known subnet, and must not be the network or broadcast address. |
|
||
|
|
| `subnet` | IPv4 subnet address. All host bits must be zero for the given mask. |
|
||
|
|
| `mask` | Prefix length. Integer 1-30. |
|
||
|
|
| `format` | IPv4 address format only. No subnet membership check. |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 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.
|