Closes #8184: Enable HTMX for embedded tables (#11518)

* Enable HTMX rendering for embedded tables

* Start converting embedded tables to use HTMX (WIP)

* Additional table conversions (WIP)

* Standardize HTMX usage for nested group models

* Enable HTMX for additional emebedded tables

* Fix HTMX table rendering for ObjectChildrenView

* Standardize usage of inc/panel_table.html

* Hide selection boxes in embedded tables
This commit is contained in:
Jeremy Stretch
2023-01-16 15:50:45 -05:00
committed by jeremystretch
parent f74a2536f1
commit 1a2dae3471
65 changed files with 381 additions and 667 deletions

View File

@@ -31,7 +31,7 @@
<tr>
<th scope="row">Circuits</th>
<td>
<a href="{% url 'circuits:circuit_list' %}?type_id={{ object.pk }}">{{ circuits_table.rows|length }}</a>
<a href="{% url 'circuits:circuit_list' %}?type_id={{ object.pk }}">{{ object.circuits.count }}</a>
</td>
</tr>
</table>
@@ -49,10 +49,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Circuits</h5>
<div class="card-body table-responsive">
{% render_table circuits_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'circuits:circuit_list' %}?type_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -40,7 +40,7 @@
<tr>
<th scope="row">Circuits</th>
<td>
<a href="{% url 'circuits:circuit_list' %}?provider={{ object.slug }}">{{ circuits_table.rows|length }}</a>
<a href="{% url 'circuits:circuit_list' %}?provider={{ object.slug }}">{{ object.circuits.count }}</a>
</td>
</tr>
</table>
@@ -60,10 +60,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Circuits</h5>
<div class="card-body table-responsive">
{% render_table circuits_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'circuits:circuit_list' %}?provider_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -50,10 +50,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Circuits</h5>
<div class="card-body table-responsive">
{% render_table circuits_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=circuits_table.paginator page=circuits_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'circuits:circuit_list' %}?provider_network_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -12,7 +12,7 @@
<div class="tab-pane show active" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
{% include 'inc/table_controls_htmx.html' %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -10,7 +10,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -45,14 +45,14 @@
<tr>
<th scope="row">Devices</th>
<td>
<a href="{% url 'dcim:device_list' %}?role_id={{ object.pk }}">{{ device_count }}</a>
<a href="{% url 'dcim:device_list' %}?role_id={{ object.pk }}">{{ object.devices.count }}</a>
</td>
</tr>
<tr>
<th>Virtual Machines</th>
<td>
{% if object.vm_role %}
<a href="{% url 'virtualization:virtualmachine_list' %}?role_id={{ object.pk }}">{{ virtualmachine_count }}</a>
<a href="{% url 'virtualization:virtualmachine_list' %}?role_id={{ object.pk }}">{{ object.virtual_machines.count }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}

View File

@@ -8,7 +8,7 @@
{% csrf_token %}
<div class="card">
<h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
<div class="card-footer noprint">
@@ -36,7 +36,7 @@
{% else %}
<div class="card">
<h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -344,13 +344,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">IP Addresses</h5>
<div class="card-body table-responsive">
{% if ipaddress_table.rows %}
{% render_table ipaddress_table 'inc/table.html' %}
{% else %}
<div class="text-muted">None</div>
{% endif %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:ipaddress_list' %}?interface_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.ipam.add_ipaddress %}
<div class="card-footer text-end noprint">
<a href="{% url 'ipam:ipaddress_add' %}?device={{ object.device.pk }}&interface={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">

View File

@@ -92,11 +92,11 @@
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Locations</h5>
<div class="card-body table-responsive">
{% render_table child_locations_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=child_locations_table.paginator page=child_locations_table.page %}
</div>
<h5 class="card-header">Child Locations</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:location_list' %}?parent_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -45,19 +45,19 @@
<tr>
<th scope="row">Device types</th>
<td>
<a href="{% url 'dcim:devicetype_list' %}?manufacturer_id={{ object.pk }}">{{ devicetypes_table.rows|length }}</a>
<a href="{% url 'dcim:devicetype_list' %}?manufacturer_id={{ object.pk }}">{{ devicetype_count }}</a>
</td>
</tr>
<tr>
<th scope="row">Module types</th>
<td>
<a href="{% url 'dcim:moduletype_list' %}?manufacturer_id={{ object.pk }}">{{ module_type_count }}</a>
<a href="{% url 'dcim:moduletype_list' %}?manufacturer_id={{ object.pk }}">{{ moduletype_count }}</a>
</td>
</tr>
<tr>
<th scope="row">Inventory Items</th>
<td>
<a href="{% url 'dcim:inventoryitem_list' %}?manufacturer_id={{ object.pk }}">{{ inventory_item_count }}</a>
<a href="{% url 'dcim:inventoryitem_list' %}?manufacturer_id={{ object.pk }}">{{ inventoryitem_count }}</a>
</td>
</tr>
</table>
@@ -76,10 +76,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Device Types</h5>
<div class="card-body table-responsive">
{% render_table devicetypes_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=devicetypes_table.paginator page=devicetypes_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:devicetype_list' %}?manufacturer_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -8,7 +8,7 @@
{% csrf_token %}
<div class="card">
<h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
<div class="card-footer noprint">
@@ -36,7 +36,7 @@
{% else %}
<div class="card">
<h5 class="card-header">{{ title }}</h5>
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -46,7 +46,7 @@
<tr>
<th scope="row">Devices</th>
<td>
<a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
<a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ device_count }}</a>
</td>
</tr>
<tr>
@@ -78,10 +78,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Devices</h5>
<div class="card-body table-responsive">
{% render_table devices_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -45,40 +45,42 @@
</div>
</div>
<div class="row my-3">
<div class="col col-md-12">
<form method="post">
{% csrf_token %}
<div class="card">
<div class="card-body table-responsive">
{% render_table powerfeed_table 'inc/table.html' %}
</div>
<div class="card-footer noprint">
{% 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 btn-sm">
<span class="mdi mdi-pencil" aria-hidden="true"></span> 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 btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> 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 btn-sm">
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_powerfeed %}
<div class="float-end">
<a href="{% url 'dcim:powerfeed_add' %}?power_panel={{ object.pk }}&return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Power Feeds
</a>
</div>
{% endif %}
</div>
<div class="col col-md-12">
<form method="post">
{% csrf_token %}
<div class="card">
<h5 class="card-header">Power Feeds</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:powerfeed_list' %}?power_panel_id={{ object.pk }}"
hx-trigger="load"
></div>
<div class="card-footer noprint">
{% 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 btn-sm">
<span class="mdi mdi-pencil" aria-hidden="true"></span> 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 btn-sm">
<span class="mdi mdi-ethernet-cable-off" aria-hidden="true"></span> 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 btn-sm">
<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span> Delete
</button>
{% endif %}
{% if perms.dcim.add_powerfeed %}
<div class="float-end">
<a href="{% url 'dcim:powerfeed_add' %}?power_panel={{ object.pk }}&return_url={% url 'dcim:powerpanel' pk=object.pk %}" class="btn btn-primary btn-sm">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Power Feeds
</a>
</div>
</form>
{% plugin_full_width_page object %}
</div>
{% endif %}
</div>
</div>
</form>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -37,7 +37,7 @@
<tr>
<th scope="row">Racks</th>
<td>
<a href="{% url 'dcim:rack_list' %}?role_id={{ object.pk }}">{{ racks_table.rows|length }}</a>
<a href="{% url 'dcim:rack_list' %}?role_id={{ object.pk }}">{{ object.racks.count }}</a>
</td>
</tr>
</table>
@@ -55,10 +55,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Racks</h5>
<div class="card-body table-responsive">
{% render_table racks_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=racks_table.paginator page=racks_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:rack_list' %}?role_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -22,9 +22,7 @@
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Region
</h5>
<h5 class="card-header">Region</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
@@ -42,7 +40,7 @@
<tr>
<th scope="row">Sites</th>
<td>
<a href="{% url 'dcim:site_list' %}?region_id={{ object.pk }}">{{ sites_table.rows|length }}</a>
<a href="{% url 'dcim:site_list' %}?region_id={{ object.pk }}">{{ object.sites.count }}</a>
</td>
</tr>
</table>
@@ -55,12 +53,11 @@
</div>
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Child Regions
</h5>
<div class="card-body table-responsive">
{% render_table child_regions_table 'inc/table.html' %}
</div>
<h5 class="card-header">Child Regions</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:region_list' %}?parent_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.dcim.add_region %}
<div class="card-footer text-end noprint">
<a href="{% url 'dcim:region_add' %}?parent={{ object.pk }}" class="btn btn-sm btn-primary">
@@ -76,10 +73,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Sites</h5>
<div class="card-body table-responsive">
{% render_table sites_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:site_list' %}?region_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -22,9 +22,7 @@
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Site Group
</h5>
<h5 class="card-header">Site Group</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
@@ -42,7 +40,7 @@
<tr>
<th scope="row">Sites</th>
<td>
<a href="{% url 'dcim:site_list' %}?group_id={{ object.pk }}">{{ sites_table.rows|length }}</a>
<a href="{% url 'dcim:site_list' %}?group_id={{ object.pk }}">{{ object.sites.count }}</a>
</td>
</tr>
</table>
@@ -55,12 +53,11 @@
</div>
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">
Child Groups
</h5>
<div class="card-body table-responsive">
{% render_table child_groups_table 'inc/table.html' %}
</div>
<h5 class="card-header">Child Groups</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:sitegroup_list' %}?parent_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.dcim.add_sitegroup %}
<div class="card-footer text-end noprint">
<a href="{% url 'dcim:sitegroup_add' %}?parent={{ object.pk }}" class="btn btn-sm btn-primary">
@@ -76,10 +73,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Sites</h5>
<div class="card-body table-responsive">
{% render_table sites_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:site_list' %}?group_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -49,6 +49,12 @@
{{ object.tenant|linkify|placeholder }}
</td>
</tr>
<tr>
<th scope="row">Interfaces</th>
<td>
<a href="{% url 'dcim:interface_list' %}?vdc_id={{ object.pk }}">{{ object.interfaces.count }}</a>
</td>
</tr>
</table>
</div>
</div>
@@ -65,10 +71,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Interfaces</h5>
<div class="card-body table-responsive">
{% render_table interfaces_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=interfaces_table.paginator page=interfaces_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:interface_list' %}?vdc_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -109,7 +109,7 @@ Context:
{% endif %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -70,9 +70,10 @@
<i class="mdi mdi-clipboard-clock"></i>
<span class="ms-1">Change Log</span>
</h6>
<div class="card-body table-responsive">
{% render_table changelog_table 'inc/table.html' %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'extras:objectchange_list' %}?sort=-time"
hx-trigger="load"
></div>
</div>
</div>
</div>

View File

@@ -4,10 +4,10 @@
{% with preferences|get_key:"pagination.placement" as paginator_placement %}
{% if paginator_placement == 'top' or paginator_placement == 'both' %}
{% include 'inc/paginator_htmx.html' with paginator=table.paginator page=table.page %}
{% include 'inc/paginator_htmx.html' with table=table paginator=table.paginator page=table.page %}
{% endif %}
{% render_table table 'inc/table_htmx.html' %}
{% if paginator_placement != 'top' %}
{% include 'inc/paginator_htmx.html' with paginator=table.paginator page=table.page %}
{% include 'inc/paginator_htmx.html' with table=table paginator=table.paginator page=table.page %}
{% endif %}
{% endwith %}

View File

@@ -7,9 +7,9 @@
<div class="btn-group btn-group-sm" role="group" aria-label="Pages">
{% if page.has_previous %}
<a href="#"
hx-get="{% querystring request page=page.previous_page_number %}"
hx-target="#object_list"
hx-push-url="true"
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="btn btn-outline-secondary"
>
<i class="mdi mdi-chevron-double-left"></i>
@@ -18,9 +18,9 @@
{% for p in page.smart_pages %}
{% if p %}
<a href="#"
hx-get="{% querystring request page=p %}"
hx-target="#object_list"
hx-push-url="true"
hx-get="{{ table.htmx_url }}{% querystring request page=p %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}"
>
{{ p }}
@@ -33,9 +33,9 @@
{% endfor %}
{% if page.has_next %}
<a href="#"
hx-get="{% querystring request page=page.next_page_number %}"
hx-target="#object_list"
hx-push-url="true"
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="btn btn-outline-secondary"
>
<i class="mdi mdi-chevron-double-right"></i>
@@ -55,9 +55,9 @@
{% for n in page.paginator.get_page_lengths %}
<li>
<a href="#"
hx-get="{% querystring request per_page=n %}"
hx-target="#object_list"
hx-push-url="true"
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>
</li>

View File

@@ -1,16 +1,14 @@
{% load render_table from django_tables2 %}
<div class="card {% if panel_class %}bg-{{ panel_class }}{% endif %}">
{% if heading %}
<h5 class="card-header">
{{ heading }}
</h5>
<div class="card {% if panel_class %}border-{{ panel_class }}{% endif %}">
{% if heading %}
<h5 class="card-header{% if panel_class %} text-{{ panel_class }}{% endif %}">{{ heading }}</h5>
{% endif %}
<div class="card-body table-responsive">
{% if table.rows %}
{% render_table table 'inc/table.html' %}
{% else %}
<div class="text-muted">None</div>
{% endif %}
<div class="card-body table-responsive">
{% if table.rows %}
{% render_table table 'inc/table.html' %}
{% else %}
<div class="text-muted">None</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -1,59 +1,56 @@
{% load django_tables2 %}
<div class="table-responsive">
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
{% if table.show_header %}
<thead>
<table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
{% if table.show_header %}
<thead>
<tr>
{% for column in table.columns %}
{% if column.orderable %}
<th {{ column.attrs.th.as_html }}>
{% if column.is_ordered %}
<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 %}
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
{% endif %}
<tbody>
{% for row in table.page.object_list|default:table.rows %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{{ cell }}</td>
{% endfor %}
</tr>
{% empty %}
{% if table.empty_text %}
<tr>
{% for column in table.columns %}
{% if column.orderable %}
<th {{ column.attrs.th.as_html }}>
{% if column.is_ordered %}
<div class="float-end">
<a href="#"
hx-get="{% querystring table.prefixed_order_by_field='' %}"
hx-target="#object_list"
hx-push-url="true"
class="text-danger"
><i class="mdi mdi-close"></i></a>
</div>
{% endif %}
<a href="#"
hx-get="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"
hx-target="#object_list"
hx-push-url="true"
>{{ column.header }}</a>
</th>
{% else %}
<th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
{% endif %}
{% endfor %}
<td colspan="{{ table.columns|length }}" class="text-center text-muted">&mdash; {{ table.empty_text }} &mdash;</td>
</tr>
</thead>
{% endif %}
<tbody>
{% for row in table.page.object_list|default:table.rows %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{{ cell }}</td>
{% endfor %}
</tr>
{% empty %}
{% if table.empty_text %}
<tr>
<td colspan="{{ table.columns|length }}" class="text-center text-muted">&mdash; {{ table.empty_text }} &mdash;</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
{% if table.has_footer %}
<tfoot>
<tr>
{% for column in table.columns %}
<td>{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% endif %}
{% endfor %}
</tbody>
{% if table.has_footer %}
<tfoot>
<tr>
{% for column in table.columns %}
<td>{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
</table>

View File

@@ -18,7 +18,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -75,17 +75,17 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Sites</h5>
<div class="card-body table-responsive">
{% render_table sites_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'dcim:site_list' %}?asn_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
<div class="card">
<h5 class="card-header">Providers</h5>
<div class="card-body table-responsive">
{% render_table providers_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=providers_table.paginator page=providers_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'circuits:provider_list' %}?asn_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -69,13 +69,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Virtual IP Addresses</h5>
<div class="card-body table-responsive">
{% if ipaddress_table.rows %}
{% render_table ipaddress_table 'inc/table.html' %}
{% else %}
<div class="text-muted">None</div>
{% endif %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:ipaddress_list' %}?fhrpgroup_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.ipam.add_ipaddress %}
<div class="card-footer text-end">
<a href="{% url 'ipam:ipaddress_add' %}?fhrpgroup={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">
@@ -84,16 +81,7 @@
</div>
{% endif %}
</div>
<div class="card">
<h5 class="card-header">Members</h5>
<div class="card-body table-responsive">
{% if members_table.rows %}
{% render_table members_table 'inc/table.html' %}
{% else %}
<div class="text-muted">None</div>
{% endif %}
</div>
</div>
{% include 'inc/panel_table.html' with table=members_table heading='Members' %}
{% plugin_full_width_page object %}
</div>
</div>

View File

@@ -114,30 +114,9 @@
<div class="col col-md-8">
{% include 'inc/panel_table.html' with table=parent_prefixes_table heading='Parent Prefixes' %}
{% if duplicate_ips_table.rows %}
{# Custom version of panel_table.html #}
<div class="card border-danger">
<h5 class="card-header">
<span class="text-danger">Duplicate IP Addresses</span>
{% if more_duplicate_ips %}
<div class="float-end">
<a type="button" class="btn btn-primary btn-sm"
{% if object.vrf %}
href="{% url 'ipam:ipaddress_list' %}?address={{ object.address.ip }}&vrf_id={{ object.vrf.pk }}"
{% else %}
href="{% url 'ipam:ipaddress_list' %}?address={{ object.address.ip }}&vrf_id=null"
{% endif %}
>Show all</a>
</div>
{% endif %}
</h5>
<div class="card-body table-responsive">
{% render_table duplicate_ips_table 'inc/table.html' %}
</div>
</div>
{% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %}
{% endif %}
<div class="my-3">
{% include 'inc/panel_table.html' with table=related_ips_table heading='Related IP Addresses' %}
</div>
{% include 'inc/panel_table.html' with table=related_ips_table heading='Related IPs' %}
{% include 'inc/panels/services.html' %}
{% plugin_right_page object %}
</div>

View File

@@ -16,7 +16,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -55,9 +55,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Terminations</h5>
<div class="card-body">
{% render_table terminations_table 'inc/table.html' %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:l2vpntermination_list' %}?l2vpn_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.ipam.add_l2vpntermination %}
<div class="card-footer text-end noprint">
<a href="{% url 'ipam:l2vpntermination_add' %}?l2vpn={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-primary btn-sm{% if not object.can_add_termination %} disabled" aria-disabled="true{% endif %}">

View File

@@ -16,7 +16,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -16,7 +16,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -18,7 +18,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -35,16 +35,16 @@
<tr>
<th scope="row">Aggregates</th>
<td>
<a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.pk }}">{{ aggregates_table.rows|length }}</a>
<a href="{% url 'ipam:aggregate_list' %}?rir_id={{ object.pk }}">{{ object.aggregates.count }}</a>
</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/tags.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
@@ -53,10 +53,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Aggregates</h5>
<div class="card-body table-responsive">
{% render_table aggregates_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=aggregates_table.paginator page=aggregates_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:aggregate_list' %}?rir_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -35,40 +35,28 @@
<tr>
<th scope="row">Prefixes</th>
<td>
<a href="{% url 'ipam:prefix_list' %}?role_id={{ object.pk }}">{{ prefixes_table.rows|length }}</a>
<a href="{% url 'ipam:prefix_list' %}?role_id={{ object.pk }}">{{ object.prefixes.count }}</a>
</td>
</tr>
<tr>
<th scope="row">IP Ranges</th>
<td>
{% with ipranges_count=object.ip_ranges.count %}
{% if ipranges_count %}
<a href="{% url 'ipam:iprange_list' %}?role_id={{ object.pk }}">{{ ipranges_count }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
{% endwith %}
<a href="{% url 'ipam:iprange_list' %}?role_id={{ object.pk }}">{{ object.ip_ranges.count }}</a>
</td>
</tr>
<tr>
<th scope="row">VLANs</th>
<td>
{% with vlans_count=object.vlans.count %}
{% if vlans_count %}
<a href="{% url 'ipam:vlan_list' %}?role_id={{ object.pk }}">{{ vlans_count }}</a>
{% else %}
{{ ''|placeholder }}
{% endif %}
{% endwith %}
<a href="{% url 'ipam:vlan_list' %}?role_id={{ object.pk }}">{{ object.vlans.count }}</a>
</td>
</tr>
</table>
</div>
</div>
{% include 'inc/panels/tags.html' %}
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/custom_fields.html' %}
{% plugin_right_page object %}
</div>
@@ -77,10 +65,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Prefixes</h5>
<div class="card-body table-responsive">
{% render_table prefixes_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=prefixes_table.paginator page=prefixes_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:prefix_list' %}?role_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -81,24 +81,22 @@
</div>
</div>
<div class="row">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">
Prefixes
</h5>
<div class="card-body table-responsive">
{% render_table prefix_table 'inc/table.html' %}
</div>
{% if perms.ipam.add_prefix %}
<div class="card-footer text-end noprint">
<a href="{% url 'ipam:prefix_add' %}?{% if object.tenant %}tenant={{ object.tenant.pk }}&{% endif %}site={{ object.site.pk }}&vlan={{ object.pk }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i>
Add a Prefix
</a>
</div>
{% endif %}
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Prefixes</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:prefix_list' %}?vlan_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.ipam.add_prefix %}
<div class="card-footer text-end noprint">
<a href="{% url 'ipam:prefix_add' %}?{% if object.tenant %}tenant={{ object.tenant.pk }}&{% endif %}site={{ object.site.pk }}&vlan={{ object.pk }}" class="btn btn-primary btn-sm">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add a Prefix
</a>
</div>
{% plugin_full_width_page object %}
{% endif %}
</div>
{% plugin_full_width_page object %}
</div>
</div>
{% endblock %}

View File

@@ -7,7 +7,7 @@
<form method="post">
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -7,7 +7,7 @@
<form method="post">
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -30,7 +30,7 @@
</div>
<div class="row px-3">
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -34,7 +34,7 @@
<tr>
<th scope="row">Contacts</th>
<td>
<a href="{% url 'tenancy:contact_list' %}?group_id={{ object.pk }}">{{ contacts_table.rows|length }}</a>
<a href="{% url 'tenancy:contact_list' %}?group_id={{ object.pk }}">{{ object.contacts.count }}</a>
</td>
</tr>
</table>
@@ -46,12 +46,11 @@
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
<div class="card">
<h5 class="card-header">
Child Groups
</h5>
<div class="card-body table-responsive">
{% render_table child_groups_table 'inc/table.html' %}
</div>
<h5 class="card-header">Child Groups</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'tenancy:contactgroup_list' %}?parent_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.tenancy.add_contactgroup %}
<div class="card-footer text-end noprint">
<a href="{% url 'tenancy:contactgroup_add' %}?parent={{ object.pk }}" class="btn btn-sm btn-primary">
@@ -63,16 +62,14 @@
{% plugin_right_page object %}
</div>
</div>
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Contacts</h5>
<div class="card-body table-responsive">
{% render_table contacts_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=contacts_table.paginator page=contacts_table.page %}
</div>
</div>
{% plugin_full_width_page object %}
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Contacts</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'tenancy:contact_list' %}?group_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>
{% endblock %}

View File

@@ -42,7 +42,7 @@
<tr>
<th scope="row">Tenants</th>
<td>
<a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
<a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ object.tenants.count }}</a>
</td>
</tr>
</table>
@@ -53,6 +53,20 @@
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
<div class="card">
<h5 class="card-header">Child Groups</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'tenancy:tenantgroup_list' %}?parent_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.tenancy.add_tenantgroup %}
<div class="card-footer text-end noprint">
<a href="{% url 'tenancy:tenantgroup_add' %}?parent={{ object.pk }}" class="btn btn-sm btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Tenant Group
</a>
</div>
{% endif %}
</div>
{% plugin_right_page object %}
</div>
</div>
@@ -60,10 +74,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Tenants</h5>
<div class="card-body table-responsive">
{% render_table tenants_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=tenants_table.paginator page=tenants_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -8,7 +8,7 @@
<form action="{% url 'virtualization:cluster_remove_devices' pk=object.pk %}" method="post">
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -8,7 +8,7 @@
<form method="post">
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -31,7 +31,7 @@
<tr>
<th scope="row">Clusters</th>
<td>
<a href="{% url 'virtualization:cluster_list' %}?group_id={{ object.pk }}">{{ clusters_table.rows|length }}</a>
<a href="{% url 'virtualization:cluster_list' %}?group_id={{ object.pk }}">{{ object.clusters.count }}</a>
</td>
</tr>
</table>
@@ -50,10 +50,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Clusters</h5>
<div class="card-body table-responsive">
{% render_table clusters_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'virtualization:cluster_list' %}?group_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -31,7 +31,7 @@
<tr>
<th scope="row">Clusters</th>
<td>
<a href="{% url 'virtualization:cluster_list' %}?type_id={{ object.pk }}">{{ clusters_table.rows|length }}</a>
<a href="{% url 'virtualization:cluster_list' %}?type_id={{ object.pk }}">{{ object.clusters.count }}</a>
</td>
</tr>
</table>
@@ -49,10 +49,10 @@
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Clusters</h5>
<div class="card-body table-responsive">
{% render_table clusters_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=clusters_table.paginator page=clusters_table.page %}
</div>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'virtualization:cluster_list' %}?type_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>

View File

@@ -9,7 +9,7 @@
{% csrf_token %}
<div class="card">
<div class="card-body" id="object_list">
<div class="card-body htmx-container table-responsive" id="object_list">
{% include 'htmx/table.html' %}
</div>
</div>

View File

@@ -80,16 +80,11 @@
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">
IP Addresses
</h5>
<div class="card-body table-responsive">
{% if ipaddress_table.rows %}
{% render_table ipaddress_table 'inc/table.html' %}
{% else %}
<div class="text-muted">None</div>
{% endif %}
</div>
<h5 class="card-header">IP Addresses</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'ipam:ipaddress_list' %}?vminterface_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.ipam.add_ipaddress %}
<div class="card-footer text-end noprint">
<a href="{% url 'ipam:ipaddress_add' %}?virtual_machine={{ object.virtual_machine.pk }}&vminterface={{ object.pk }}&return_url={{ object.get_absolute_url }}" class="btn btn-sm btn-primary">

View File

@@ -40,7 +40,7 @@
<tr>
<th scope="row">Wireless LANs</th>
<td>
<a href="{% url 'wireless:wirelesslan_list' %}?group_id={{ object.pk }}">{{ wirelesslans_table.rows|length }}</a>
<a href="{% url 'wireless:wirelesslan_list' %}?group_id={{ object.pk }}">{{ object.wirelesslans.count }}</a>
</td>
</tr>
</table>
@@ -51,17 +51,31 @@
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
<div class="card">
<h5 class="card-header">Child Groups</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'wireless:wirelesslangroup_list' %}?parent_id={{ object.pk }}"
hx-trigger="load"
></div>
{% if perms.wireless.add_wirelesslangroup %}
<div class="card-footer text-end noprint">
<a href="{% url 'wireless:wirelesslangroup_add' %}?parent={{ object.pk }}" class="btn btn-sm btn-primary">
<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Wireless LAN Group
</a>
</div>
{% endif %}
</div>
{% plugin_right_page object %}
</div>
</div>
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<div class="card-header">Wireless LANs</div>
<div class="card-body table-responsive">
{% render_table wirelesslans_table 'inc/table.html' %}
{% include 'inc/paginator.html' with paginator=wirelesslans_table.paginator page=wirelesslans_table.page %}
</div>
<h5 class="card-header">Wireless LANs</h5>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'wireless:wirelesslan_list' %}?group_id={{ object.pk }}"
hx-trigger="load"
></div>
</div>
{% plugin_full_width_page object %}
</div>