Adds copy content button (#12584)

* adds copy content button #12499

* adds newline

* Omit hash mark from target string

* Clean up HTML element IDs

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
This commit is contained in:
Abhimanyu Saharan 2023-07-06 23:49:55 +05:30 committed by GitHub
parent 8143c6e03b
commit 62bdb90f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 69 additions and 23 deletions

View File

@ -19,14 +19,22 @@ __all__ = (
AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>') AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
AGGREGATE_COPY_BUTTON = """
{% copy_content record.pk prefix="aggregate_" %}
"""
PREFIX_LINK = """ PREFIX_LINK = """
{% if record.pk %} {% if record.pk %}
<a href="{{ record.get_absolute_url }}">{{ record.prefix }}</a> <a href="{{ record.get_absolute_url }}" id="prefix_{{ record.pk }}">{{ record.prefix }}</a>
{% else %} {% else %}
<a href="{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.site %}&site={{ object.site.pk }}{% endif %}{% if object.tenant %}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}{% endif %}">{{ record.prefix }}</a> <a href="{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.site %}&site={{ object.site.pk }}{% endif %}{% if object.tenant %}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}{% endif %}">{{ record.prefix }}</a>
{% endif %} {% endif %}
""" """
PREFIX_COPY_BUTTON = """
{% copy_content record.pk prefix="prefix_" %}
"""
PREFIX_LINK_WITH_DEPTH = """ PREFIX_LINK_WITH_DEPTH = """
{% load helpers %} {% load helpers %}
{% if record.depth %} {% if record.depth %}
@ -40,7 +48,7 @@ PREFIX_LINK_WITH_DEPTH = """
IPADDRESS_LINK = """ IPADDRESS_LINK = """
{% if record.pk %} {% if record.pk %}
<a href="{{ record.get_absolute_url }}">{{ record.address }}</a> <a href="{{ record.get_absolute_url }}" id="ipaddress_{{ record.pk }}">{{ record.address }}</a>
{% elif perms.ipam.add_ipaddress %} {% elif perms.ipam.add_ipaddress %}
<a href="{% url 'ipam:ipaddress_add' %}?address={{ record.1 }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}" class="btn btn-sm btn-success">{% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available</a> <a href="{% url 'ipam:ipaddress_add' %}?address={{ record.1 }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}" class="btn btn-sm btn-success">{% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available</a>
{% else %} {% else %}
@ -48,6 +56,10 @@ IPADDRESS_LINK = """
{% endif %} {% endif %}
""" """
IPADDRESS_COPY_BUTTON = """
{% copy_content record.pk prefix="ipaddress_" %}
"""
IPADDRESS_ASSIGN_LINK = """ IPADDRESS_ASSIGN_LINK = """
<a href="{% url 'ipam:ipaddress_edit' pk=record.pk %}?{% if request.GET.interface %}interface={{ request.GET.interface }}{% elif request.GET.vminterface %}vminterface={{ request.GET.vminterface }}{% endif %}&return_url={{ request.GET.return_url }}">{{ record }}</a> <a href="{% url 'ipam:ipaddress_edit' pk=record.pk %}?{% if request.GET.interface %}interface={{ request.GET.interface }}{% elif request.GET.vminterface %}vminterface={{ request.GET.vminterface }}{% endif %}&return_url={{ request.GET.return_url }}">{{ record }}</a>
""" """
@ -99,7 +111,11 @@ class RIRTable(NetBoxTable):
class AggregateTable(TenancyColumnsMixin, NetBoxTable): class AggregateTable(TenancyColumnsMixin, NetBoxTable):
prefix = tables.Column( prefix = tables.Column(
linkify=True, linkify=True,
verbose_name='Aggregate' verbose_name='Aggregate',
attrs={
# Allow the aggregate to be copied to the clipboard
'a': {'id': lambda record: f"aggregate_{record.pk}"}
}
) )
date_added = tables.DateColumn( date_added = tables.DateColumn(
format="Y-m-d", format="Y-m-d",
@ -116,6 +132,9 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable):
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='ipam:aggregate_list' url_name='ipam:aggregate_list'
) )
actions = columns.ActionsColumn(
extra_buttons=AGGREGATE_COPY_BUTTON
)
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = Aggregate model = Aggregate
@ -242,6 +261,9 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='ipam:prefix_list' url_name='ipam:prefix_list'
) )
actions = columns.ActionsColumn(
extra_buttons=PREFIX_COPY_BUTTON
)
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = Prefix model = Prefix
@ -348,6 +370,9 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='ipam:ipaddress_list' url_name='ipam:ipaddress_list'
) )
actions = columns.ActionsColumn(
extra_buttons=IPADDRESS_COPY_BUTTON
)
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = IPAddress model = IPAddress

Binary file not shown.

Binary file not shown.

View File

@ -2,7 +2,7 @@ import Clipboard from 'clipboard';
import { getElements } from './util'; import { getElements } from './util';
export function initClipboard(): void { export function initClipboard(): void {
for (const element of getElements('a.copy-token', 'button.copy-secret')) { for (const element of getElements('a.copy-content')) {
new Clipboard(element); new Clipboard(element);
} }
} }

View File

@ -39,9 +39,7 @@
<th scope="row">Path</th> <th scope="row">Path</th>
<td> <td>
<span class="font-monospace" id="datafile_path">{{ object.path }}</span> <span class="font-monospace" id="datafile_path">{{ object.path }}</span>
<a class="btn btn-sm btn-primary copy-token" data-clipboard-target="#datafile_path" title="Copy to clipboard"> {% copy_content "datafile_path" %}
<i class="mdi mdi-content-copy"></i>
</a>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -56,9 +54,7 @@
<th scope="row">SHA256 Hash</th> <th scope="row">SHA256 Hash</th>
<td> <td>
<span class="font-monospace" id="datafile_hash">{{ object.hash }}</span> <span class="font-monospace" id="datafile_hash">{{ object.hash }}</span>
<a class="btn btn-sm btn-primary copy-token" data-clipboard-target="#datafile_hash" title="Copy to clipboard"> {% copy_content "datafile_hash" %}
<i class="mdi mdi-content-copy"></i>
</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -194,12 +194,13 @@
<th scope="row">Primary IPv4</th> <th scope="row">Primary IPv4</th>
<td> <td>
{% if object.primary_ip4 %} {% if object.primary_ip4 %}
<a href="{{ object.primary_ip4.get_absolute_url }}">{{ object.primary_ip4.address.ip }}</a> <a href="{{ object.primary_ip4.get_absolute_url }}" id="primary_ip4">{{ object.primary_ip4.address.ip }}</a>
{% if object.primary_ip4.nat_inside %} {% if object.primary_ip4.nat_inside %}
(NAT for <a href="{{ object.primary_ip4.nat_inside.get_absolute_url }}">{{ object.primary_ip4.nat_inside.address.ip }}</a>) (NAT for <a href="{{ object.primary_ip4.nat_inside.get_absolute_url }}">{{ object.primary_ip4.nat_inside.address.ip }}</a>)
{% elif object.primary_ip4.nat_outside.exists %} {% elif object.primary_ip4.nat_outside.exists %}
(NAT: {% for nat in object.primary_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}) (NAT: {% for nat in object.primary_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
{% endif %} {% endif %}
{% copy_content "primary_ip4" %}
{% else %} {% else %}
{{ ''|placeholder }} {{ ''|placeholder }}
{% endif %} {% endif %}
@ -209,12 +210,13 @@
<th scope="row">Primary IPv6</th> <th scope="row">Primary IPv6</th>
<td> <td>
{% if object.primary_ip6 %} {% if object.primary_ip6 %}
<a href="{{ object.primary_ip6.get_absolute_url }}">{{ object.primary_ip6.address.ip }}</a> <a href="{{ object.primary_ip6.get_absolute_url }}" id="primary_ip6">{{ object.primary_ip6.address.ip }}</a>
{% if object.primary_ip6.nat_inside %} {% if object.primary_ip6.nat_inside %}
(NAT for <a href="{{ object.primary_ip6.nat_inside.get_absolute_url }}">{{ object.primary_ip6.nat_inside.address.ip }}</a>) (NAT for <a href="{{ object.primary_ip6.nat_inside.get_absolute_url }}">{{ object.primary_ip6.nat_inside.address.ip }}</a>)
{% elif object.primary_ip6.nat_outside.exists %} {% elif object.primary_ip6.nat_outside.exists %}
(NAT: {% for nat in object.primary_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}) (NAT: {% for nat in object.primary_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
{% endif %} {% endif %}
{% copy_content "primary_ip6" %}
{% else %} {% else %}
{{ ''|placeholder }} {{ ''|placeholder }}
{% endif %} {% endif %}

View File

@ -31,13 +31,23 @@
<tr> <tr>
<th scope="row">Primary IPv4</th> <th scope="row">Primary IPv4</th>
<td> <td>
{{ object.primary_ip4|linkify|placeholder }} {% if object.primary_ip4 %}
<a href="{{ object.primary_ip4.get_absolute_url }}" id="primary_ip4">{{ object.primary_ip4 }}</a>
{% copy_content "primary_ip4" %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">Primary IPv6</th> <th scope="row">Primary IPv6</th>
<td> <td>
{{ object.primary_ip6|linkify|placeholder }} {% if object.primary_ip6 %}
<a href="{{ object.primary_ip6.get_absolute_url }}" id="primary_ip6">{{ object.primary_ip6 }}</a>
{% copy_content "primary_ip6" %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -8,7 +8,7 @@
<div class="col col-md-12"> <div class="col col-md-12">
{% if not settings.ALLOW_TOKEN_RETRIEVAL %} {% if not settings.ALLOW_TOKEN_RETRIEVAL %}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
<i class="mdi mdi-alert"></i> Tokens cannot be retrieved at a later time. You must <a href="#" class="copy-token" data-clipboard-target="#token_id" title="Copy to clipboard">copy the token value</a> below and store it securely. <i class="mdi mdi-alert"></i> Tokens cannot be retrieved at a later time. You must <a href="#" class="copy-content" data-clipboard-target="#token_id" title="Copy to clipboard">copy the token value</a> below and store it securely.
</div> </div>
{% endif %} {% endif %}
<div class="card"> <div class="card">
@ -19,9 +19,7 @@
<th scope="row">Key</th> <th scope="row">Key</th>
<td> <td>
<div class="float-end"> <div class="float-end">
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_id" title="Copy to clipboard"> {% copy_content "token_id" %}
<i class="mdi mdi-content-copy"></i>
</a>
</div> </div>
<div id="token_id">{{ key }}</div> <div id="token_id">{{ key }}</div>
</td> </td>

View File

@ -46,12 +46,13 @@
<th scope="row">Primary IPv4</th> <th scope="row">Primary IPv4</th>
<td> <td>
{% if object.primary_ip4 %} {% if object.primary_ip4 %}
<a href="{% url 'ipam:ipaddress' pk=object.primary_ip4.pk %}">{{ object.primary_ip4.address.ip }}</a> <a href="{% url 'ipam:ipaddress' pk=object.primary_ip4.pk %}" id="primary_ip4">{{ object.primary_ip4.address.ip }}</a>
{% if object.primary_ip4.nat_inside %} {% if object.primary_ip4.nat_inside %}
(NAT for <a href="{{ object.primary_ip4.nat_inside.get_absolute_url }}">{{ object.primary_ip4.nat_inside.address.ip }}</a>) (NAT for <a href="{{ object.primary_ip4.nat_inside.get_absolute_url }}">{{ object.primary_ip4.nat_inside.address.ip }}</a>)
{% elif object.primary_ip4.nat_outside.exists %} {% elif object.primary_ip4.nat_outside.exists %}
(NAT: {% for nat in object.primary_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}) (NAT: {% for nat in object.primary_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
{% endif %} {% endif %}
{% copy_content "primary_ip4" %}
{% else %} {% else %}
{{ ''|placeholder }} {{ ''|placeholder }}
{% endif %} {% endif %}
@ -61,12 +62,13 @@
<th scope="row">Primary IPv6</th> <th scope="row">Primary IPv6</th>
<td> <td>
{% if object.primary_ip6 %} {% if object.primary_ip6 %}
<a href="{% url 'ipam:ipaddress' pk=object.primary_ip6.pk %}">{{ object.primary_ip6.address.ip }}</a> <a href="{% url 'ipam:ipaddress' pk=object.primary_ip6.pk %}" id="primary_ip6">{{ object.primary_ip6.address.ip }}</a>
{% if object.primary_ip6.nat_inside %} {% if object.primary_ip6.nat_inside %}
(NAT for <a href="{{ object.primary_ip6.nat_inside.get_absolute_url }}">{{ object.primary_ip6.nat_inside.address.ip }}</a>) (NAT for <a href="{{ object.primary_ip6.nat_inside.get_absolute_url }}">{{ object.primary_ip6.nat_inside.address.ip }}</a>)
{% elif object.primary_ip6.nat_outside.exists %} {% elif object.primary_ip6.nat_outside.exists %}
(NAT: {% for nat in object.primary_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}) (NAT: {% for nat in object.primary_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
{% endif %} {% endif %}
{% copy_content "primary_ip6" %}
{% else %} {% else %}
{{ ''|placeholder }} {{ ''|placeholder }}
{% endif %} {% endif %}

View File

@ -12,9 +12,7 @@ ALLOWED_IPS = """{{ value|join:", " }}"""
COPY_BUTTON = """ COPY_BUTTON = """
{% if settings.ALLOW_TOKEN_RETRIEVAL %} {% if settings.ALLOW_TOKEN_RETRIEVAL %}
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_{{ record.pk }}" title="Copy to clipboard"> {% copy_content record.pk prefix="token_" color="success" %}
<i class="mdi mdi-content-copy"></i>
</a>
{% endif %} {% endif %}
""" """

View File

@ -0,0 +1,3 @@
<a class="btn btn-sm {{ color }} copy-content" data-clipboard-target="{{ target }}" title="Copy to clipboard">
<i class="mdi mdi-content-copy"></i>
</a>

View File

@ -6,6 +6,7 @@ from utilities.utils import dict_to_querydict
__all__ = ( __all__ = (
'badge', 'badge',
'checkmark', 'checkmark',
'copy_content',
'customfield_value', 'customfield_value',
'tag', 'tag',
) )
@ -79,6 +80,17 @@ def checkmark(value, show_false=True, true='Yes', false='No'):
} }
@register.inclusion_tag('builtins/copy_content.html')
def copy_content(target, prefix=None, color='primary'):
"""
Display a copy button to copy the content of a field.
"""
return {
'target': f'#{prefix or ""}{target}',
'color': f'btn-{color}'
}
@register.inclusion_tag('builtins/htmx_table.html', takes_context=True) @register.inclusion_tag('builtins/htmx_table.html', takes_context=True)
def htmx_table(context, viewname, return_url=None, **kwargs): def htmx_table(context, viewname, return_url=None, **kwargs):
""" """