{# Invisible timed poller. Lives INSIDE the swapped fragment so its `url` is regenerated on every swap — that preserves the current sort/page/filters across live refreshes. CSP-safe (a plain `every Ns` trigger needs no eval; no `[expr]` filter is used). #} {% macro live_poll(url, target, secs=10) -%} {%- endmacro %} {# Dashboard stat tile: a coloured icon square + number/label, optionally a link. #} {% macro _stat_icon(name) -%} {%- if name == 'targets' %} {%- elif name == 'enabled' %} {%- elif name == 'groups' %} {%- elif name == 'tags' %} {%- elif name == 'schedules' %} {%- elif name == 'sets' %} {%- elif name == 'files' %} {%- elif name == 'size' %} {%- endif -%} {%- endmacro %} {% macro dstat(href, variant, num, label, icon) -%} {% if href %}{% else %}{% endif %} {%- endmacro %} {# Small coloured icon for a card heading (colour baked per name). #} {% macro hicon(name) -%} {%- set color = {'reach': 'info', 'system': 'accent', 'health': 'ok', 'latency': 'warn', 'runs': 'accent'}.get(name, 'muted') -%} {%- endmacro %} {# Render up to `limit` chips as links to the targets view filtered by that tag/group; collapse the remainder into a "+N" chip whose tooltip lists the overflow. #} {% macro chips(items, limit=6, cls='') %} {%- set kind = 'group' if 'group' in cls else 'tag' -%} {%- for it in items[:limit] %}{{ it }}{% endfor -%} {%- if items|length > limit %}+{{ items|length - limit }}{%- endif -%} {% endmacro %} {# Capability chip for a group's allow_download / allow_restore tri-state. #} {% macro cap_chip(val) -%} {%- if val is none %}inherit {%- elif val %}allow {%- else %}deny{% endif -%} {%- endmacro %} {# Tag/member pill input. Renders a plain text input (works without JS); app.js enhances it into removable pills with autocomplete + create-on-the-spot from `suggest`. #} {% macro tag_input(name, value, suggest, placeholder='add…') -%}
{%- endmacro %} {% macro latency_cell(lat, hist=none) %} {%- if lat is none %} {%- elif not lat.reachable %}down {%- elif hist and (hist|reject('none')|list|length >= 2) %}{{ '%.1f'|format(lat.rtt_ms) if lat.rtt_ms is not none else '' }} {%- elif lat.rtt_ms is not none %}{{ "%.1f"|format(lat.rtt_ms) }} ms {%- else %}up{% endif -%} {% endmacro %} {# Green check / red cross for an included-or-not state (e.g. a backup type in a run). #} {% macro check_cross(val, yes='included', no='not included') -%} {%- if val %}{{ _svg(''|safe) }} {%- else %}{{ _svg(''|safe) }}{% endif -%} {%- endmacro %} {# Boolean state as an icon (styleguide: never render booleans as yes/no text). #} {% macro bool_check(val, yes='Enabled', no='Disabled') -%} {%- if val %}{{ _svg(''|safe) }} {%- else %}{{ _svg(''|safe) }}{% endif -%} {%- endmacro %} {# A single tri-state status orb: blue (in a running job, takes precedence) overrides green (enabled) / red (disabled). Never more than one orb. #} {% macro status_orb(enabled, active) -%} {%- if active %} {%- elif enabled %} {%- else %}{% endif -%} {%- endmacro %} {# A unique device identifier (IP/host, RouterOS, firmware, serial, SSH fingerprint, token): a fixed-width value in a rounded, click-to-copy box (see .copyval / style-guide rule 15). `copy` overrides what's copied when it differs from what's shown (e.g. show fingerprint, copy the full public key). #} {% macro copyval(value, label, copy=none) -%} {%- endmacro %} {# A flat count rendered as a pill (grey rounded badge) for easier visual scanning; an optional href makes the pill itself the link. #} {% macro count_pill(value, href=none, title=none) -%} {%- if href %}{{ value }} {%- else %}{{ value }}{% endif -%} {%- endmacro %} {% macro backups_cell(r) %} {%- if r.backups %}{{ count_pill(r.backups, '/backups/' ~ (r.name|urlencode)) }} {%- else %}{{ count_pill(0) }}{% endif -%} {% endmacro %} {# ---- Row actions: compact TEXT buttons (styleguide rule 2). Colour-coded with the existing palette only — green = run/positive, red = edit/delete/restore (destructive or caution), neutral = benign (download/preview/browse). The element type + htmx/data attributes are preserved so behaviour (modal preview, confirm-delete) is unchanged. #} {% macro _svg(paths) -%} {%- endmacro %} {% macro icon_edit(href, label='Edit') -%} {{ label }} {%- endmacro %} {# Preview opens in the modal (app.js intercepts data-preview); href is the no-JS fallback. #} {% macro icon_preview(href, label='Preview') -%} {{ label }} {%- endmacro %} {% macro icon_download(href, label='Download') -%} {{ label }} {%- endmacro %} {% macro icon_restore(href, label='Restore') -%} {{ label }} {%- endmacro %} {# Render a run/schedule scope as links: target -> its detail, group/tag -> filtered list. #} {% macro scope_link(kind, value) -%} {%- if not value %}{{ kind }} {%- elif kind == 'target' %}{{ kind }}: {{ value }} {%- elif kind == 'group' %}{{ kind }}: {{ value }} {%- elif kind == 'tag' %}{{ kind }}: {{ value }} {%- else %}{{ kind }}: {{ value }}{% endif -%} {%- endmacro %} {% macro icon_browse(href, label='Browse') -%} {{ label }} {%- endmacro %} {# Destructive delete: an htmx text button (hx-confirm shows a native confirm dialog; the CSRF token rides the body's hx-headers). The route returns an HX-Redirect. #} {% macro icon_delete(action, confirm='Delete this? This cannot be undone.', label='Delete') -%} {%- endmacro %} {# ---- Pagination (web spec: page-size selector + pager on every list) ---- The page-size {% for s in [10, 25, 50, 100, 150, 200, 500] %}{% endfor %} {%- endmacro %} {% macro pager(pg, base, target, pagebase, include='', extra='') -%} {% if pg and pg.total %} {% endif %} {%- endmacro %}