Compare commits
27 Commits
c7a5433796
...
38bc314713
Author | SHA1 | Date |
---|---|---|
Jeremy Stretch | 38bc314713 | |
Jeremy Stretch | c7a63e02f2 | |
Jeremy Stretch | 41f21d31ef | |
Jeremy Stretch | 6c540e56ff | |
Jeremy Stretch | 5fcc0fc2a4 | |
Jeremy Stretch | ec364c6fc2 | |
Jeremy Stretch | 4c92397d71 | |
Jeremy Stretch | 1af5799f2d | |
Jeremy Stretch | e37c943d2b | |
Jeremy Stretch | 16ceaef601 | |
Jeremy Stretch | ebab17d851 | |
Jeremy Stretch | a36467ce3c | |
Jeremy Stretch | 3061e16304 | |
Jeremy Stretch | 8ba5963ada | |
Jeremy Stretch | 4d85e9ce34 | |
Jeremy Stretch | 7d86d0989d | |
Jeremy Stretch | e735be7167 | |
Jeremy Stretch | 236c561677 | |
Jeremy Stretch | c377d8360c | |
Jeremy Stretch | 648a14ad98 | |
Jeremy Stretch | d224f734d5 | |
Jeremy Stretch | 881b994038 | |
Jeremy Stretch | a78fb8033c | |
Jeremy Stretch | fa4f4893a3 | |
Jeremy Stretch | 4f3f2142f4 | |
Jeremy Stretch | a742f2ebfd | |
Jeremy Stretch | 6d06152573 |
|
@ -25,6 +25,7 @@ from netbox.views import generic
|
|||
from netbox.views.generic.base import BaseObjectView
|
||||
from netbox.views.generic.mixins import TableMixin
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.query import count_related
|
||||
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
|
@ -320,7 +321,7 @@ class BackgroundTaskListView(TableMixin, BaseRQView):
|
|||
table = self.get_table(data, request, False)
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if request.htmx:
|
||||
if htmx_partial(request):
|
||||
return render(request, 'htmx/table.html', {
|
||||
'table': table,
|
||||
})
|
||||
|
@ -489,8 +490,8 @@ class WorkerListView(TableMixin, BaseRQView):
|
|||
table = self.get_table(data, request, False)
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if request.htmx:
|
||||
if request.htmx.target != 'object_list':
|
||||
if htmx_partial(request):
|
||||
if not request.htmx.target:
|
||||
table.embedded = True
|
||||
# Hide selection checkboxes
|
||||
if 'pk' in table.base_columns:
|
||||
|
|
|
@ -20,6 +20,7 @@ from netbox.views import generic
|
|||
from netbox.views.generic.mixins import TableMixin
|
||||
from utilities.data import shallow_compare_dict
|
||||
from utilities.forms import ConfirmationForm, get_field_value
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.query import count_related
|
||||
from utilities.querydict import normalize_querydict
|
||||
|
@ -1224,7 +1225,7 @@ class ScriptResultView(TableMixin, generic.ObjectView):
|
|||
}
|
||||
|
||||
# If this is an HTMX request, return only the result HTML
|
||||
if request.htmx:
|
||||
if htmx_partial(request):
|
||||
response = render(request, 'extras/htmx/script_result.html', context)
|
||||
if job.completed or not job.started:
|
||||
response.status_code = 286
|
||||
|
|
|
@ -8,9 +8,11 @@ def settings_and_registry(request):
|
|||
"""
|
||||
Expose Django settings and NetBox registry stores in the template context. Example: {{ settings.DEBUG }}
|
||||
"""
|
||||
user_preferences = request.user.config if request.user.is_authenticated else {}
|
||||
return {
|
||||
'settings': django_settings,
|
||||
'config': get_config(),
|
||||
'registry': registry,
|
||||
'preferences': request.user.config if request.user.is_authenticated else {},
|
||||
'preferences': user_preferences,
|
||||
'htmx_navigation': user_preferences.get('ui.htmx_navigation', False) == 'true'
|
||||
}
|
||||
|
|
|
@ -23,6 +23,14 @@ PREFERENCES = {
|
|||
),
|
||||
default='light',
|
||||
),
|
||||
'ui.htmx_navigation': UserPreference(
|
||||
label=_('HTMX Navigation'),
|
||||
choices=(
|
||||
('', _('Disabled')),
|
||||
('true', _('Enabled')),
|
||||
),
|
||||
default=False
|
||||
),
|
||||
'locale.language': UserPreference(
|
||||
label=_('Language'),
|
||||
choices=(
|
||||
|
|
|
@ -23,6 +23,7 @@ from utilities.error_handlers import handle_protectederror
|
|||
from utilities.exceptions import AbortRequest, AbortTransaction, PermissionsViolation
|
||||
from utilities.forms import BulkRenameForm, ConfirmationForm, restrict_form_fields
|
||||
from utilities.forms.bulk_import import BulkImportForm
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.views import GetReturnURLMixin, get_viewname
|
||||
from .base import BaseMultiObjectView
|
||||
|
@ -161,8 +162,8 @@ class ObjectListView(BaseMultiObjectView, ActionsMixin, TableMixin):
|
|||
table = self.get_table(self.queryset, request, has_bulk_actions)
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if request.htmx:
|
||||
if request.htmx.target != 'object_list':
|
||||
if htmx_partial(request):
|
||||
if not request.htmx.target:
|
||||
table.embedded = True
|
||||
# Hide selection checkboxes
|
||||
if 'pk' in table.base_columns:
|
||||
|
|
|
@ -17,6 +17,7 @@ from extras.signals import clear_events
|
|||
from utilities.error_handlers import handle_protectederror
|
||||
from utilities.exceptions import AbortRequest, PermissionsViolation
|
||||
from utilities.forms import ConfirmationForm, restrict_form_fields
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.querydict import normalize_querydict, prepare_cloned_fields
|
||||
from utilities.views import GetReturnURLMixin, get_viewname
|
||||
|
@ -138,7 +139,7 @@ class ObjectChildrenView(ObjectView, ActionsMixin, TableMixin):
|
|||
table = self.get_table(table_data, request, has_bulk_actions)
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if request.htmx:
|
||||
if htmx_partial(request):
|
||||
return render(request, 'htmx/table.html', {
|
||||
'object': instance,
|
||||
'table': table,
|
||||
|
@ -226,7 +227,7 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
|
|||
restrict_form_fields(form, request.user)
|
||||
|
||||
# If this is an HTMX request, return only the rendered form HTML
|
||||
if request.htmx:
|
||||
if htmx_partial(request):
|
||||
return render(request, 'htmx/form.html', {
|
||||
'form': form,
|
||||
})
|
||||
|
@ -482,7 +483,7 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||
instance = self.alter_object(self.queryset.model(), request)
|
||||
|
||||
# If this is an HTMX request, return only the rendered form HTML
|
||||
if request.htmx:
|
||||
if htmx_partial(request):
|
||||
return render(request, 'htmx/form.html', {
|
||||
'form': form,
|
||||
})
|
||||
|
|
|
@ -17,6 +17,7 @@ from netbox.forms import SearchForm
|
|||
from netbox.search import LookupTypes
|
||||
from netbox.search.backends import search_backend
|
||||
from netbox.tables import SearchTable
|
||||
from utilities.htmx import htmx_partial
|
||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
|
||||
__all__ = (
|
||||
|
@ -104,7 +105,7 @@ class SearchView(View):
|
|||
}).configure(table)
|
||||
|
||||
# If this is an HTMX request, return only the rendered table HTML
|
||||
if request.htmx:
|
||||
if htmx_partial(request):
|
||||
return render(request, 'htmx/table.html', {
|
||||
'table': table,
|
||||
})
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,11 +1,12 @@
|
|||
import { getElements, isTruthy } from './util';
|
||||
import { initButtons } from './buttons';
|
||||
import { initClipboard } from './clipboard'
|
||||
import { initSelects } from './select';
|
||||
import { initObjectSelector } from './objectSelector';
|
||||
import { initBootstrap } from './bs';
|
||||
import { initMessages } from './messages';
|
||||
|
||||
function initDepedencies(): void {
|
||||
for (const init of [initButtons, initSelects, initObjectSelector, initBootstrap]) {
|
||||
for (const init of [initButtons, initClipboard, initSelects, initObjectSelector, initBootstrap, initMessages]) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +16,5 @@ function initDepedencies(): void {
|
|||
* elements.
|
||||
*/
|
||||
export function initHtmx(): void {
|
||||
for (const element of getElements('[hx-target]')) {
|
||||
const targetSelector = element.getAttribute('hx-target');
|
||||
if (isTruthy(targetSelector)) {
|
||||
for (const target of getElements(targetSelector)) {
|
||||
target.addEventListener('htmx:afterSettle', initDepedencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of getElements('[hx-trigger=load]')) {
|
||||
element.addEventListener('htmx:afterSettle', initDepedencies);
|
||||
}
|
||||
document.addEventListener('htmx:afterSettle', initDepedencies);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@import '../node_modules/@tabler/core/src/scss/vendor/tom-select';
|
||||
|
||||
// Overrides of external libraries
|
||||
@import 'overrides/bootstrap';
|
||||
@import 'overrides/tabler';
|
||||
|
||||
// Transitional styling to ease migration of templates from NetBox v3.x
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// Disable smooth scrolling for intra-page links
|
||||
html {
|
||||
scroll-behavior: auto !important;
|
||||
}
|
|
@ -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 #}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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" %}.
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -55,7 +55,8 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass):
|
|||
class UserConfigForm(forms.ModelForm, metaclass=UserConfigFormMetaclass):
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'locale.language', 'pagination.per_page', 'pagination.placement', 'ui.colormode', name=_('User Interface')
|
||||
'locale.language', 'pagination.per_page', 'pagination.placement', 'ui.colormode', 'ui.htmx_navigation',
|
||||
name=_('User Interface')
|
||||
),
|
||||
FieldSet('data_format', name=_('Miscellaneous')),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
__all__ = (
|
||||
'htmx_partial',
|
||||
)
|
||||
|
||||
PAGE_CONTAINER_ID = 'page-content'
|
||||
|
||||
|
||||
def htmx_partial(request):
|
||||
"""
|
||||
Determines whether to render partial (versus complete) HTML content
|
||||
in response to an HTMX request, based on the target element.
|
||||
"""
|
||||
return request.htmx and request.htmx.target != PAGE_CONTAINER_ID
|
|
@ -1,4 +1,5 @@
|
|||
<div class="card-body htmx-container table-responsive p-0"
|
||||
hx-get="{% url viewname %}{% if url_params %}?{{ url_params.urlencode }}{% endif %}"
|
||||
hx-trigger="load"
|
||||
hx-target="this"
|
||||
hx-trigger="load" hx-select="table" hx-swap="innerHTML"
|
||||
></div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load i18n %}
|
||||
{% if url %}
|
||||
<button type="submit" name="_delete" formaction="{{ url }}" class="btn btn-red">
|
||||
<button type="submit" name="_delete" {% formaction %}="{{ url }}" class="btn btn-red">
|
||||
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete Selected" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load i18n %}
|
||||
{% if url %}
|
||||
<button type="submit" name="_edit" formaction="{{ url }}" class="btn btn-yellow">
|
||||
<button type="submit" name="_edit" {% formaction %}="{{ url }}" class="btn btn-yellow">
|
||||
<i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit Selected" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
<a href="#"
|
||||
hx-get="{{ url }}"
|
||||
hx-target="#htmx-modal-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-select="form"
|
||||
class="btn btn-red"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#htmx-modal"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% load helpers %}
|
||||
{% load navigation %}
|
||||
|
||||
<ul class="navbar-nav pt-lg-2">
|
||||
<ul class="navbar-nav pt-lg-2" {% htmx_boost %}>
|
||||
{% for menu, groups in nav_items %}
|
||||
<li class="nav-item dropdown">
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ __all__ = (
|
|||
'checkmark',
|
||||
'copy_content',
|
||||
'customfield_value',
|
||||
'formaction',
|
||||
'tag',
|
||||
)
|
||||
|
||||
|
@ -113,3 +114,14 @@ def htmx_table(context, viewname, return_url=None, **kwargs):
|
|||
'viewname': viewname,
|
||||
'url_params': url_params,
|
||||
}
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def formaction(context):
|
||||
"""
|
||||
Replace the 'formaction' attribute on an HTML element with the appropriate HTMX attributes
|
||||
if HTMX navigation is enabled (per the user's preferences).
|
||||
"""
|
||||
if context.get('htmx_navigation', False):
|
||||
return 'hx-push-url="true" hx-post'
|
||||
return 'formaction'
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from typing import Dict
|
||||
from django import template
|
||||
from django.template import Context
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from netbox.navigation.menu import MENUS
|
||||
|
||||
__all__ = (
|
||||
'nav',
|
||||
'htmx_boost',
|
||||
)
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ register = template.Library()
|
|||
|
||||
|
||||
@register.inclusion_tag("navigation/menu.html", takes_context=True)
|
||||
def nav(context: Context) -> Dict:
|
||||
def nav(context):
|
||||
"""
|
||||
Render the navigation menu.
|
||||
"""
|
||||
|
@ -40,6 +40,31 @@ def nav(context: Context) -> Dict:
|
|||
nav_items.append((menu, groups))
|
||||
|
||||
return {
|
||||
"nav_items": nav_items,
|
||||
"request": context["request"]
|
||||
'nav_items': nav_items,
|
||||
'htmx_navigation': context['htmx_navigation']
|
||||
}
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def htmx_boost(context, target='#page-content', select='#page-content'):
|
||||
"""
|
||||
Renders the HTML attributes needed to effect HTMX boosting within an element if
|
||||
HTMX navigation is enabled for the request. The target and select parameters are
|
||||
rendered as `hx-target` and `hx-select`, respectively. For example:
|
||||
|
||||
<div id="page-content" {% htmx_boost %}>
|
||||
|
||||
If HTMX navigation is not enabled, the tag renders no content.
|
||||
"""
|
||||
if not context.get('htmx_navigation', False):
|
||||
return ''
|
||||
hx_params = {
|
||||
'hx-boost': 'true',
|
||||
'hx-target': target,
|
||||
'hx-select': select,
|
||||
'hx-swap': 'outerHTML show:window:top',
|
||||
}
|
||||
htmx_params = ' '.join([
|
||||
f'{k}="{v}"' for k, v in hx_params.items()
|
||||
])
|
||||
return mark_safe(htmx_params)
|
||||
|
|
Loading…
Reference in New Issue