{# core/ui/templates/partials/components/ui_macros.html Generische UI-Makros – AstrapiFlaskUi V3 Standard-URL-Konventionen: CRUD: POST /api/{module}/{item}/run Debug: POST /api/{module}/{item}/run?debug=true Logs: GET /api/{module}/{item}/logs #} {% macro edit_button(module=None, item=None, container_id=None, loading_id=None, item_prefix='') %} {% endmacro %} {% macro delete_button(module=None, item=None, container_id=None, loading_id=None, description=None, item_prefix='') %} {% endmacro %} {% macro toggle_switch(module=None, item=None, container_id=None, loading_id=None, enabled=False, description=None) %} {% endmacro %} {% macro toggle_button(module=None, item=None, container_id=None, loading_id=None, enabled=False, description=None) %} {{ toggle_switch(module=module, item=item, container_id=container_id, loading_id=loading_id, enabled=enabled, description=description) }} {% endmacro %} {% macro add_button(module=None, container_id=None, loading_id=None) %} {% endmacro %} {% macro action_button(label, url, title=None, css_class="btn-ghost btn-sm", target="body", swap="beforeend") %} {% endmacro %} {% macro status_inline(status) %} {% set _s = status | lower if status else '' %} {% if _s == 'ok' %} OK {% elif _s == 'error' or _s.startswith('fehler') %} Fehler {% elif _s == 'warning' %} Warnung {% elif _s == 'running' or _s == 'building' %} läuft {% elif _s == 'pending' %} Pending {% elif _s %} {{ status }} {% else %} Neu {% endif %} {% endmacro %} {% macro refresh_button(endpoint="/", container_id=None, loading_id=None) %} {% endmacro %} {# ── Runnable-Modul-Makros ────────────────────────────────────────────────── #} {# Standard-Konvention: POST /api/{module}/{item}/run[?debug=true] #} {# GET /api/{module}/{item}/logs #} {% macro run_button(module=None, item=None, container_id=None, enabled=True) %} {% endmacro %} {% macro run_debug_button(module=None, item=None, container_id=None) %} {% endmacro %} {% macro log_button(module=None, item=None) %} {% endmacro %} {# ── Button-Basis ─────────────────────────────────────────────────────────── #} {# Alle Text-Buttons bauen auf btn() auf. #} {# Ausnahmen: Icon-Buttons (btn-icon), Sidebar-Nav, Pagination, Kontext-Menüs #} {# btn(label, variant, size, type, onclick, attrs) Basis-Baustein für alle Text-Buttons. Inhalt via label oder {% call %}. variant – ghost | primary | danger (default: ghost) size – sm | '' (leer = kein Modifier) (default: sm) attrs – rohe HTML-Attribute als String, z.B. 'hx-get="..." hx-target="..."' #} {# btn(label, variant, size, type, onclick, attrs, busy_label) busy_label: Text während einer laufenden htmx-Anfrage. htmx setzt dafür die Klasse .htmx-request auf das auslösende Element; die CSS-Regeln in app.css blenden dann Label gegen Spinner + busy_label aus und sperren den Button. #} {% macro btn(label='', variant='ghost', size='sm', type='button', onclick=None, attrs='', busy_label=None) %} {% endmacro %} {% macro btn_cancel(label='Abbrechen') %}{{ btn(label, onclick='closeModal(this)') }}{% endmacro %} {% macro btn_close(label='Schließen') %}{{ btn(label, onclick='closeModal(this)') }}{% endmacro %} {# btn_save(label, type, save_id, disabled) Primär-Button für Formular-Submit. #} {% macro btn_save(label='Speichern', type='submit', save_id=None, disabled=False) %} {% set _id = ('id="' ~ save_id ~ '" ') if save_id else '' %} {{ btn(label, variant='primary', type=type, attrs=_id ~ ('disabled' if disabled else '')) }} {% endmacro %} {# ── Form-Steuerelemente ──────────────────────────────────────────────────── #} {# Alle Formular-Controls bauen auf diesen Macros auf (G-013). #} {# Ausnahmen: hidden-Inputs, multiselect (badge-pill), list (Alpine.js), #} {# filter-selects im List-Header (header_control), borg-Browser #} {# form_group(label, required, help) Caller-Macro: Wrapper für ein Formularfeld (Label + Control + Fehler-Span). #} {% macro form_group(label='', required=False, help='') %}
{% if label %} {% endif %} {{ caller() }} {% if help %}{{ help }}{% endif %}
{% endmacro %} {# form_input(name, value, type, required, placeholder, attrs) Rendert . Passwort-Felder: value wird nie gerendert. Varianten via attrs: style="font-family:var(--mono);", readonly, autocomplete, HTMX-Attribute. #} {% macro form_input(name, value='', type='text', required=False, placeholder='', attrs='') %} {% endmacro %} {# form_select(name, options, value, required, placeholder, attrs) Rendert {% if caller is defined %} {{ caller() }} {% else %} {% if placeholder %}{% endif %} {% for opt in options %} {% if opt is mapping %} {% else %} {% endif %} {% endfor %} {% endif %} {% endmacro %} {# form_textarea(name, value, rows, placeholder, attrs) Rendert {% endmacro %} {# toggle_field(name, checked, label, field_id) Toggle-Switch mit Hidden-Input (liefert immer "0" oder "1"). Ersetzt alle raw hidden+checkbox+button-Konstrukte in Forms. #} {% macro toggle_field(name, checked=False, label='Aktiviert', field_id='') %} {% set _id = field_id if field_id else ('tf-' ~ name) %} {% endmacro %} {# ── Modal-Standard-Komponenten (legacy, nicht mehr in neuen Templates verwenden) ── #} {# Neue Templates erben von ui/dialogs/dialog_core.html (oder _form / _confirm). #} {# modal_title(title, icon, is_edit) Legacy-Makro – ersetzt durch {% block dialog_title %} in dialog_core.html. #} {% macro modal_title(title='', icon=None, is_edit=False) %}
{{ title }}
{% endmacro %} {# modal_footer(enabled, enabled_id, save_label, save_disabled, has_toggle, save_id, save_type) Rendert den Standard-Footer eines Modals (Toggle + Abbrechen + Speichern). #} {% macro modal_footer(enabled=True, enabled_id='modal-enabled', save_label='Speichern', save_disabled=False, has_toggle=True, save_id=None, save_type='submit') %} {% endmacro %} {# modal_footer_simple(close_label) Footer mit nur einem Schließen-Button. #} {% macro modal_footer_simple(close_label="Schließen") %} {% endmacro %} {# modal_footer_actions() Footer mit extra Aktions-Buttons (via caller) VOR dem Schließen-Button. Verwendung: {% call modal_footer_actions() %}[extra buttons]{% endcall %} #} {% macro modal_footer_actions() %} {% endmacro %} {# modal_footer_split() Footer mit split-Layout: links caller-Inhalt (Aktionen + Info), rechts Schließen. Verwendung: {% call modal_footer_split() %}[linker Inhalt]{% endcall %} #} {% macro modal_footer_split() %} {% endmacro %} {# icon(name) Rendert ein inline-SVG anhand des Icon-Namens (13×13 px). Wird von card_action_button() und extern verwendet. Bekannte Namen: search, bar-chart, play, bug, file-text, terminal, external-link, info, refresh #} {% macro icon(name) -%} {%- endmacro %} {# card_action_button(action, item, item_data, container_id, loading_id) Rendert einen Card-Footer-Button aus einer card_actions-Eintragsdefinition. action – Dict aus modul.yaml card_actions: title – Tooltip icon – Icon-Name (siehe icon()-Makro) style – optional: 'run', 'debug', 'log', 'danger' → btn-icon-{style} hx_get – HTMX GET-URL (alternativ zu hx_post) hx_post – HTMX POST-URL (alternativ zu hx_get) hx_headers – HTMX-Headers als JSON-String hx_target – HTMX-Target (default: #main-content) hx_swap – HTMX-Swap (default: innerHTML) hx_push_url – HTMX push-url disabled_if_off – bool: Button deaktivieren wenn item_data.enabled=false Platzhalter in URLs und hx_target: {item}, {description}, {container_id}, {loading_id} #} {% macro card_action_button(action, item, item_data={}, container_id='', loading_id='') %} {%- if not action.show_if_field or item_data.get(action.show_if_field) -%} {%- if not action.hide_if_field_value or item_data.get(action.hide_if_field_value.field) != action.hide_if_field_value.value -%} {%- set _subs = [ ('{item}', item | string), ('{description}', item_data.description | default(item | string)), ('{container_id}', container_id | string), ('{loading_id}', loading_id | string), ] -%} {%- macro _sub(s) -%} {%- set ns = namespace(v=s) -%} {%- for k, v in _subs %}{%- set ns.v = ns.v | replace(k, v) -%}{%- endfor -%} {%- for k, v in item_data.items() %}{%- set ns.v = ns.v | replace('{' + k + '}', v | string) -%}{%- endfor -%} {{- ns.v -}} {%- endmacro -%} {%- if action.href -%} {{ icon(action.icon) }} {%- else -%} {%- endif -%} {%- endif -%} {%- endif %} {% endmacro %} {# ═══════════════════════════════════════════════════════════════════════════ #} {# Deklaratives UI-System – Render-Makros für controls.py-Dataklassen #} {# ═══════════════════════════════════════════════════════════════════════════ #} {# col_header(col) Rendert ein -Element für eine Col-Instanz aus controls.py. #} {% macro col_header(col) %}{{ col.label }} {% endmacro %} {# col_cell(col, item_name, item_data) Rendert ein -Element für eine Col-Instanz. col – controls.Col-Instanz item_name – String-Key des Eintrags item_data – Dict mit den Eintragsdaten #} {% macro col_cell(col, item_name, item_data) %} {%- set _val = item_data.get(col.key) if item_data is mapping else none -%} {%- if col.type == 'text' -%} {{ _val or '—' }} {%- elif col.type == 'trunc' -%} {{ _val or '—' }} {%- elif col.type == 'remote_path' -%} {%- set _rhost = resolve_remote_host(item_data.get(col.remote_key)) if item_data.get(col.remote_key) else '—' -%} {%- set _rlabel = 'Lokal' if _rhost == '—' else _rhost -%} {{ _rlabel }}:{{ item_data.get(col.path_key) or '—' }} {%- elif col.type == 'remote_host' -%} {%- set _rhost = resolve_remote_host(item_data.get(col.remote_key)) if item_data.get(col.remote_key) else '—' -%} {{ 'Lokal' if _rhost == '—' else _rhost }} {%- elif col.type == 'join' -%} {%- set _list = _val if (_val is iterable and _val is not string) else [] -%} {{ _list | join(col.sep) or '—' }} {%- elif col.type == 'mono' -%} {{ _val or '—' }} {%- elif col.type == 'badge_enum' -%} {%- set _bd = col.values.get(_val | string, {}) if col.values and _val else {} -%} {%- if _bd %}{{ _bd.label | default(_val) }} {%- elif _val %}{{ _val }} {%- else %}—{% endif -%} {%- elif col.type == 'badge_list' -%} {%- set _list = _val if (_val is iterable and _val is not string) else [] -%} {%- if _list -%}
{%- for v in _list -%} {%- set _bd = col.values.get(v | string, {}) if col.values else {} -%} {{ _bd.label | default(v) }} {%- endfor -%}
{%- else %}—{% endif -%} {%- elif col.type == 'link' -%} {%- if _val %}{{ _val }} {%- else %}—{% endif -%} {%- elif col.type == 'composed' -%} {%- set _composed = col.template | replace('{item_name}', item_name | string) | replace('{key}', _val | string) -%} {{ _composed if _val else '—' }} {%- elif col.type == 'version_badge' -%} {%- set _upd = item_data.get(col.remote_key) if col.remote_key else '' -%} {{- _val or '—' -}} {%- if _upd and _upd != _val %}{{ _upd }}{% endif -%} {%- elif col.type == 'status' -%} {{ status_inline(_val) }} {%- else -%} {{ _val or '—' }} {%- endif %} {% endmacro %} {# header_control(ctrl, module, container_id, loading_id) Rendert ein einzelnes Header-Control (filter_select oder action_button). ctrl – controls.HeaderControl-Instanz module – Modul-Key (String) container_id – Container-ID für Standard-HTMX-Target loading_id – Loading-Indicator-ID #} {% macro header_control(ctrl, module, container_id, loading_id='', filter_defs=[]) %} {%- if ctrl.type == 'filter_select' -%} {%- set _hx_get = ctrl.hx_get or ('/ui/' ~ module ~ '/content') -%} {%- set _hx_target = ctrl.hx_target or ('#' ~ container_id) -%} {%- set _hx_swap = ctrl.hx_swap or 'innerHTML' -%} {%- set _hx_include = ctrl.hx_include or ('[data-filter-for=\'' ~ module ~ '\']') -%} {%- set _data_key = ctrl.data_attr or ('data-filter-for="' ~ module ~ '"') -%} {%- set _opts = ctrl.options if ctrl.options else [] -%} {%- if not _opts and ctrl.options_fn -%}{%- set _opts = ctrl.options_fn() -%}{%- endif -%} {%- set _ns = namespace(active='') -%} {%- for fd in filter_defs -%}{%- if fd.param == ctrl.name -%}{%- set _ns.active = fd.active -%}{%- endif -%}{%- endfor -%} {%- elif ctrl.type == 'action_button' -%} {%- set _attrs = (('hx-get="' ~ ctrl.hx_get ~ '" ') if ctrl.hx_get else '') ~ (('hx-post="' ~ ctrl.hx_post ~ '" ') if ctrl.hx_post else '') ~ (('hx-target="' ~ (ctrl.hx_target or 'body') ~ '" hx-swap="' ~ (ctrl.hx_swap or 'beforeend') ~ '"') if (ctrl.hx_get or ctrl.hx_post) else '') -%} {{ btn(ctrl.label, variant=ctrl.style or 'primary', attrs=_attrs) }} {%- endif %} {% endmacro %} {% macro last_run_cell(value) %}{%- if value and ' ' in value -%}{%- set parts = value.split(' ', 1) -%}{%- set dp = parts[0].split('-') -%}{%- set date_str = dp[2] ~ '.' ~ dp[1] ~ '.' ~ dp[0] if dp|length == 3 and dp[0]|length == 4 else parts[0] -%}{{ date_str }}{{ parts[1] }}{%- else -%}{{ value or '—' }}{%- endif %}{% endmacro %}