Closes #14736: Enable HTMX navigation globally (#15158)

* Enable HTMX boosting

* Refactor HTMX properties for tables

* Fix dashboard object list widget

* Disable scrolling to page content

* Fix initialization of TomSelect dropdowns after HTMX loading

* Replace formaction properties with hx-post

* Fix quick search field on object list view

* Reinitialize copy-to-clipboard buttons upon HTMX load

* Disable scrolling effect for intra-page navigation

* Introduce user preference for toggling HTMX navigation

* Enable HTMX navigation only when selected by user

* Pass htmx_navigation context

* Fix display of confirmation form when deleting an object

* Disable HTMX boosting for rack elevation SVG downloads

* Fix dyanmic form rendering

* Introduce htmx_boost template tag; enable HTMX for user menu

* Use out-of-band sap to update footer stamp

* Fix display of toasts after form submission

* Fix user preference selection

* Misc cleanup

* Rename render_partial() to htmx_partial()

* Add docstring to htmx_boost template tag

* Disable HTMX for user preferences form to force a full page refresh on changes
This commit is contained in:
Jeremy Stretch
2024-03-28 11:51:38 -04:00
committed by GitHub
parent 04d8db7c52
commit 744be59a4d
54 changed files with 213 additions and 160 deletions

View File

@@ -6,7 +6,7 @@
{% block title %}{% trans "User Preferences" %}{% endblock %}
{% block content %}
<form method="post" action="" id="preferences-update">
<form method="post" action="" hx-disable="true" id="preferences-update">
{% csrf_token %}
{# Built-in preferences #}

View File

@@ -15,6 +15,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, viewport-fit=cover" />
<meta name="htmx-config" content='{"scrollBehavior": "auto"}'>
{# Page title #}
<title>{% block title %}{% trans "Home" %}{% endblock %} | NetBox</title>

View File

@@ -58,8 +58,48 @@ Blocks:
<i class="mdi mdi-lightbulb-on"></i>
</button>
</div>
{# User menu #}
{% include 'inc/user_menu.html' %}
{% if request.user.is_authenticated %}
<div class="nav-item dropdown">
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
<div class="d-xl-block ps-2">
<div>{{ request.user }}</div>
<div class="mt-1 small text-secondary">{% if request.user.is_staff %}Staff{% else %}User{% endif %}</div>
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow" {% htmx_boost %}>
{% if config.DJANGO_ADMIN_ENABLED and request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> {% trans "Django Admin" %}
</a>
{% endif %}
<a href="{% url 'account:profile' %}" class="dropdown-item">
<i class="mdi mdi-account"></i> {% trans "Profile" %}
</a>
<a href="{% url 'account:bookmarks' %}" class="dropdown-item">
<i class="mdi mdi-bookmark"></i> {% trans "Bookmarks" %}
</a>
<a href="{% url 'account:preferences' %}" class="dropdown-item">
<i class="mdi mdi-wrench"></i> {% trans "Preferences" %}
</a>
<a href="{% url 'account:usertoken_list' %}" class="dropdown-item">
<i class="mdi mdi-key"></i> {% trans "API Tokens" %}
</a>
<div class="dropdown-divider"></div>
<a href="{% url 'logout' %}" class="dropdown-item">
<i class="mdi mdi-logout-variant"></i> {% trans "Log Out" %}
</a>
</div>
</div>
{% else %}
<div class="btn-group ps-2">
<a class="btn btn-primary" type="button" href="{% url 'login' %}?next={{ request.path }}">
<i class="mdi mdi-login-variant"></i> {% trans "Log In" %}
</a>
</div>
{% endif %}
{# /User menu #}
</div>
{# Search box #}
@@ -79,6 +119,7 @@ Blocks:
{# Page content #}
<div class="page-wrapper">
<div id="page-content" {% htmx_boost %}>
{# Page header #}
{% block header %}
@@ -122,6 +163,8 @@ Blocks:
{% endif %}
{# /Bottom banner #}
</div>
{# Page footer #}
<footer class="footer footer-transparent d-print-none py-2">
<div class="container-fluid d-flex justify-content-between align-items-center">
@@ -173,7 +216,7 @@ Blocks:
{# /Footer links #}
{# Footer text #}
<ul class="list-inline list-inline-dots mb-0">
<ul class="list-inline list-inline-dots mb-0" id="footer-stamp" hx-swap-oob="true">
<li class="list-inline-item">
{% annotated_now %} {% now 'T' %}
</li>

View File

@@ -10,7 +10,7 @@
{% endif %}
{% if 'bulk_rename' in actions %}
{% with bulk_rename_view=model|validated_viewname:"bulk_rename" %}
<button type="submit" name="_rename" formaction="{% url bulk_rename_view %}" class="btn btn-outline-warning">
<button type="submit" name="_rename" {% formaction %}="{% url bulk_rename_view %}" class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename Selected" %}
</button>
{% endwith %}

View File

@@ -5,7 +5,7 @@
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
{% if 'bulk_edit' in actions and bulk_edit_view %}
<button type="submit" name="_edit"
formaction="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
{% formaction %}="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
class="btn btn-warning">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
</button>
@@ -14,7 +14,7 @@
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
{% if 'bulk_rename' in actions and bulk_rename_view %}
<button type="submit" name="_rename"
formaction="{% url bulk_rename_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>

View File

@@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -11,7 +11,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -7,7 +7,7 @@
{% with bulk_disconnect_view=child_model|validated_viewname:"bulk_disconnect" %}
{% if 'bulk_disconnect' in actions and bulk_disconnect_view %}
<button type="submit" name="_disconnect"
formaction="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_disconnect_view %}?return_url={{ return_url }}"
class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>

View File

@@ -11,63 +11,63 @@
<ul class="dropdown-menu">
{% if perms.dcim.add_consoleport %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_consoleport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_consoleport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Console Ports" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_consoleserverport %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_consoleserverport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item ">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_consoleserverport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item ">
{% trans "Console Server Ports" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_powerport %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_powerport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_powerport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Power Ports" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_poweroutlet %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_poweroutlet' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_poweroutlet' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Power Outlets" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_interface %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_interface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
class="dropdown-item">{% trans "Interfaces" %}
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_interface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Interfaces" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_rearport %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_rearport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_rearport' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Rear Ports" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_devicebay %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_devicebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_devicebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Device Bays" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_modulebay %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_modulebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_modulebay' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Module Bays" %}
</button>
</li>
{% endif %}
{% if perms.dcim.add_inventoryitem %}
<li>
<button type="submit" formaction="{% url 'dcim:device_bulk_add_inventoryitem' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'dcim:device_bulk_add_inventoryitem' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Inventory Items" %}
</button>
</li>
@@ -78,7 +78,7 @@
{% if 'bulk_edit' in actions %}
<div class="btn-group" role="group">
{% bulk_edit_button model query_params=request.GET %}
<button type="submit" name="_rename" formaction="{% url 'dcim:device_bulk_rename' %}?return_url={% url 'dcim:device_list' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-outline-warning">
<button type="submit" name="_rename" {% formaction %}="{% url 'dcim:device_bulk_rename' %}?return_url={% url 'dcim:device_list' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename" %}
</button>
</div>

View File

@@ -7,7 +7,7 @@
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
{% if 'bulk_edit' in actions and bulk_edit_view %}
<button type="submit" name="_edit"
formaction="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
{% formaction %}="{% url bulk_edit_view %}?device={{ object.pk }}&return_url={{ return_url }}"
class="btn btn-warning">
<i class="mdi mdi-pencil" aria-hidden="true"></i> Edit Selected
</button>
@@ -16,7 +16,7 @@
{% with bulk_rename_view=child_model|validated_viewname:"bulk_rename" %}
{% if 'bulk_rename' in actions and bulk_rename_view %}
<button type="submit" name="_rename"
formaction="{% url bulk_rename_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_rename_view %}?return_url={{ return_url }}"
class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> Rename
</button>

View File

@@ -3,7 +3,7 @@
<object data="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" class="rack_elevation"></object>
</div>
<div class="text-center mt-3">
<a class="btn btn-outline-primary" href="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}">
<a class="btn btn-outline-primary" href="{% url 'dcim-api:rack-elevation' pk=object.pk %}?face={{face}}&render=svg{% if extra_params %}&{{ extra_params }}{% endif %}" hx-boost="false">
<i class="mdi mdi-file-download"></i> {% trans "Download SVG" %}
</a>
</div>

View File

@@ -13,13 +13,13 @@
</div>
<div class="card-footer d-print-none">
{% if table.rows %}
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ return_url }}" class="btn btn-warning">
<button type="submit" name="_edit" {% formaction %}="{% url table.Meta.model|viewname:"bulk_rename" %}?return_url={{ return_url }}" class="btn btn-warning">
<span class="mdi mdi-pencil-outline" aria-hidden="true"></span> {% trans "Rename" %}
</button>
<button type="submit" name="_edit" formaction="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ return_url }}" class="btn btn-warning">
<button type="submit" name="_edit" {% formaction %}="{% url table.Meta.model|viewname:"bulk_edit" %}?return_url={{ return_url }}" class="btn btn-warning">
<span class="mdi mdi-pencil" aria-hidden="true"></span> {% trans "Edit" %}
</button>
<button type="submit" name="_delete" formaction="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ return_url }}" class="btn btn-danger">
<button type="submit" name="_delete" {% formaction %}="{% url table.Meta.model|viewname:"bulk_delete" %}?return_url={{ return_url }}" class="btn btn-danger">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
</button>
{% endif %}

View File

@@ -52,17 +52,17 @@
{% htmx_table 'dcim:powerfeed_list' power_panel_id=object.pk %}
<div class="card-footer d-print-none">
{% if perms.dcim.change_powerfeed %}
<button type="submit" name="_edit" formaction="{% url 'dcim:powerfeed_bulk_edit' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-warning">
<button type="submit" name="_edit" {% formaction %}="{% url 'dcim:powerfeed_bulk_edit' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-warning">
<span class="mdi mdi-pencil" aria-hidden="true"></span> {% trans "Edit" %}
</button>
{% endif %}
{% if perms.dcim.delete_cable %}
<button type="submit" name="_disconnect" formaction="{% url 'dcim:powerfeed_bulk_disconnect' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-outline-danger">
<button type="submit" name="_disconnect" {% formaction %}="{% url 'dcim:powerfeed_bulk_disconnect' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-outline-danger">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> {% trans "Disconnect" %}
</button>
{% endif %}
{% if perms.dcim.delete_powerfeed %}
<button type="submit" name="_delete" formaction="{% url 'dcim:powerfeed_bulk_delete' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-danger">
<button type="submit" name="_delete" {% formaction %}="{% url 'dcim:powerfeed_bulk_delete' %}?return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-danger">
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> {% trans "Delete" %}
</button>
{% endif %}

View File

@@ -3,7 +3,7 @@
{% block bulk_buttons %}
{% if perms.extras.sync_configcontext %}
<button type="submit" name="_sync" formaction="{% url 'extras:configcontext_bulk_sync' %}" class="btn btn-primary">
<button type="submit" name="_sync" {% formaction %}="{% url 'extras:configcontext_bulk_sync' %}" class="btn btn-primary">
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync Data" %}
</button>
{% endif %}

View File

@@ -3,7 +3,7 @@
{% block bulk_buttons %}
{% if perms.extras.sync_configtemplate %}
<button type="submit" name="_sync" formaction="{% url 'extras:configtemplate_bulk_sync' %}" class="btn btn-primary">
<button type="submit" name="_sync" {% formaction %}="{% url 'extras:configtemplate_bulk_sync' %}" class="btn btn-primary">
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync Data" %}
</button>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% load i18n %}
{% if htmx_url and has_permission %}
<div class="htmx-container" hx-get="{{ htmx_url }}" hx-trigger="load"></div>
<div class="htmx-container" hx-get="{{ htmx_url }}" hx-trigger="load" hx-target="this" hx-select="table" hx-swap="innerHTML"></div>
{% elif htmx_url %}
<div class="text-muted text-center">
<i class="mdi mdi-lock-outline"></i> {% trans "No permission to view this content" %}.

View File

@@ -3,7 +3,7 @@
{% block bulk_buttons %}
{% if perms.extras.sync_configcontext %}
<button type="submit" name="_sync" formaction="{% url 'extras:exporttemplate_bulk_sync' %}" class="btn btn-primary">
<button type="submit" name="_sync" {% formaction %}="{% url 'extras:exporttemplate_bulk_sync' %}" class="btn btn-primary">
<i class="mdi mdi-sync" aria-hidden="true"></i> {% trans "Sync Data" %}
</button>
{% endif %}

View File

@@ -21,7 +21,7 @@
{% with bulk_edit_view=child_model|validated_viewname:"bulk_edit" %}
{% if 'bulk_edit' in actions and bulk_edit_view %}
<button type="submit" name="_edit"
formaction="{% url bulk_edit_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_edit_view %}?return_url={{ return_url }}"
class="btn btn-warning">
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit Selected" %}
</button>
@@ -35,7 +35,7 @@
{% with bulk_delete_view=child_model|validated_viewname:"bulk_delete" %}
{% if 'bulk_delete' in actions and bulk_delete_view %}
<button type="submit"
formaction="{% url bulk_delete_view %}?return_url={{ return_url }}"
{% formaction %}="{% url bulk_delete_view %}?return_url={{ return_url }}"
class="btn btn-danger">
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete Selected" %}
</button>

View File

@@ -56,7 +56,7 @@ Context:
<form action="" method="post" enctype="multipart/form-data" class="object-edit mt-5">
{% csrf_token %}
<div id="form_fields">
<div id="form_fields" hx-disinherit="hx-select hx-swap">
{% block form %}
{% include 'htmx/form.html' %}
{% endblock form %}

View File

@@ -1,6 +1,6 @@
{% load helpers %}
<div id="django-messages" class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="django-messages" class="toast-container position-fixed bottom-0 end-0 p-3" hx-swap-oob="true">
{# Non-Field Form Errors #}
{% if form and form.non_field_errors %}

View File

@@ -2,7 +2,12 @@
{% load i18n %}
{% if page %}
<div class="d-flex justify-content-between align-items-center border-top p-2">
<div
class="d-flex justify-content-between align-items-center border-top p-2"
hx-target="closest .htmx-container"
hx-disinherit="hx-select hx-swap"
{% if not table.embedded %}hx-push-url="true"{% endif %}
>
{# Pages carousel #}
{% if paginator.num_pages > 1 %}
@@ -13,12 +18,7 @@
{% if page.has_previous %}
<li class="page-item">
{% if htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=page.previous_page_number %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="page-link"
>
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request page=page.previous_page_number %}" class="page-link">
<i class="mdi mdi-chevron-left"></i>
</a>
{% else %}
@@ -34,12 +34,7 @@
{% for p in page.smart_pages %}
<li class="page-item{% if page.number == p %} active" aria-current="page{% endif %}">
{% if p and htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=p %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="page-link"
>
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request page=p %}" class="page-link">
{{ p }}
</a>
{% elif p %}
@@ -57,12 +52,7 @@
{% if page.has_next %}
<li class="page-item">
{% if htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=page.next_page_number %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="page-link"
>
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request page=page.next_page_number %}" class="page-link">
<i class="mdi mdi-chevron-right"></i>
</a>
{% else %}
@@ -97,12 +87,7 @@
<div class="dropdown-menu">
{% for n in page.paginator.get_page_lengths %}
{% if htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request per_page=n %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="dropdown-item"
>{{ n }}</a>
<a href="#" hx-get="{{ table.htmx_url }}{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
{% else %}
<a href="{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
{% endif %}

View File

@@ -3,7 +3,7 @@
<div class="row mb-3">
<div class="col-auto d-print-none">
<div class="input-group input-group-flat me-2 quicksearch">
<div class="input-group input-group-flat me-2 quicksearch" hx-disinherit="hx-select hx-swap">
<input type="search" results="5" name="q" id="quicksearch" class="form-control px-2 py-1" placeholder="Quick search"
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
<span class="input-group-text py-1">

View File

@@ -1,7 +1,11 @@
{% load django_tables2 %}
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
{% if table.show_header %}
<thead>
<thead
hx-target="closest .htmx-container"
hx-disinherit="hx-select hx-swap"
{% if not table.embedded %} hx-push-url="true"{% endif %}
>
<tr>
{% for column in table.columns %}
{% if column.orderable %}
@@ -10,16 +14,12 @@
<div class="float-end">
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field='' %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="text-danger"
><i class="mdi mdi-close"></i></a>
</div>
{% endif %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
>{{ column.header }}</a>
</th>
{% else %}

View File

@@ -1,41 +0,0 @@
{% load i18n %}
{% if request.user.is_authenticated %}
<div class="nav-item dropdown">
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
<div class="d-xl-block ps-2">
<div>{{ request.user }}</div>
<div class="mt-1 small text-secondary">{% if request.user.is_staff %}Staff{% else %}User{% endif %}</div>
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
{% if config.DJANGO_ADMIN_ENABLED and request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> {% trans "Django Admin" %}
</a>
{% endif %}
<a href="{% url 'account:profile' %}" class="dropdown-item">
<i class="mdi mdi-account"></i> {% trans "Profile" %}
</a>
<a href="{% url 'account:bookmarks' %}" class="dropdown-item">
<i class="mdi mdi-bookmark"></i> {% trans "Bookmarks" %}
</a>
<a href="{% url 'account:preferences' %}" class="dropdown-item">
<i class="mdi mdi-wrench"></i> {% trans "Preferences" %}
</a>
<a href="{% url 'account:usertoken_list' %}" class="dropdown-item">
<i class="mdi mdi-key"></i> {% trans "API Tokens" %}
</a>
<div class="dropdown-divider"></div>
<a href="{% url 'logout' %}" class="dropdown-item">
<i class="mdi mdi-logout-variant"></i> {% trans "Log Out" %}
</a>
</div>
</div>
{% else %}
<div class="btn-group ps-2">
<a class="btn btn-primary" type="button" href="{% url 'login' %}?next={{ request.path }}">
<i class="mdi mdi-login-variant"></i> {% trans "Log In" %}
</a>
</div>
{% endif %}

View File

@@ -5,7 +5,7 @@
{{ block.super }}
{% if 'bulk_remove_devices' in actions %}
<button type="submit" name="_remove"
formaction="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}?return_url={{ return_url }}"
{% formaction %}="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}?return_url={{ return_url }}"
class="btn btn-danger">
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> {% trans "Remove Selected" %}
</button>

View File

@@ -6,7 +6,7 @@
{{ block.super }}
{% if 'bulk_rename' in actions %}
<button type="submit" name="_rename"
formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ return_url }}"
{% formaction %}="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ return_url }}"
class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename" %}
</button>

View File

@@ -6,7 +6,7 @@
{{ block.super }}
{% if 'bulk_rename' in actions %}
<button type="submit" name="_rename"
formaction="{% url 'virtualization:virtualdisk_bulk_rename' %}?return_url={{ return_url }}"
{% formaction %}="{% url 'virtualization:virtualdisk_bulk_rename' %}?return_url={{ return_url }}"
class="btn btn-outline-warning">
<i class="mdi mdi-pencil-outline" aria-hidden="true"></i> {% trans "Rename" %}
</button>

View File

@@ -10,14 +10,14 @@
<ul class="dropdown-menu">
{% if perms.virtualization.add_vminterface %}
<li>
<button type="submit" formaction="{% url 'virtualization:virtualmachine_bulk_add_vminterface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'virtualization:virtualmachine_bulk_add_vminterface' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Interfaces" %}
</button>
</li>
{% endif %}
{% if perms.virtualization.add_virtualdisk %}
<li>
<button type="submit" formaction="{% url 'virtualization:virtualmachine_bulk_add_virtualdisk' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
<button type="submit" {% formaction %}="{% url 'virtualization:virtualmachine_bulk_add_virtualdisk' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" class="dropdown-item">
{% trans "Virtual Disks" %}
</button>
</li>