Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions netbox/ipam/tables/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@

AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')

AGGREGATE_COPY_BUTTON = """
{% copy_content record.pk prefix="aggregate_" %}
"""

PREFIX_LINK = """
{% 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 %}
<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 %}
"""

PREFIX_COPY_BUTTON = """
{% copy_content record.pk prefix="prefix_" %}
"""

PREFIX_LINK_WITH_DEPTH = """
{% load helpers %}
{% if record.depth %}
Expand All @@ -40,14 +48,18 @@

IPADDRESS_LINK = """
{% 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 %}
<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 %}
{% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available
{% endif %}
"""

IPADDRESS_COPY_BUTTON = """
{% copy_content record.pk prefix="ipaddress_" %}
"""

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>
"""
Expand Down Expand Up @@ -99,7 +111,11 @@ class Meta(NetBoxTable.Meta):
class AggregateTable(TenancyColumnsMixin, NetBoxTable):
prefix = tables.Column(
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(
format="Y-m-d",
Expand All @@ -116,6 +132,9 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable):
tags = columns.TagColumn(
url_name='ipam:aggregate_list'
)
actions = columns.ActionsColumn(
extra_buttons=AGGREGATE_COPY_BUTTON
)

class Meta(NetBoxTable.Meta):
model = Aggregate
Expand Down Expand Up @@ -242,6 +261,9 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
tags = columns.TagColumn(
url_name='ipam:prefix_list'
)
actions = columns.ActionsColumn(
extra_buttons=PREFIX_COPY_BUTTON
)

class Meta(NetBoxTable.Meta):
model = Prefix
Expand Down Expand Up @@ -348,6 +370,9 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
tags = columns.TagColumn(
url_name='ipam:ipaddress_list'
)
actions = columns.ActionsColumn(
extra_buttons=IPADDRESS_COPY_BUTTON
)

class Meta(NetBoxTable.Meta):
model = IPAddress
Expand Down
2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox/project-static/src/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Clipboard from 'clipboard';
import { getElements } from './util';

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);
}
}
8 changes: 2 additions & 6 deletions netbox/templates/core/datafile.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ <h5 class="card-header">Data File</h5>
<th scope="row">Path</th>
<td>
<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">
<i class="mdi mdi-content-copy"></i>
</a>
{% copy_content "datafile_path" %}
</td>
</tr>
<tr>
Expand All @@ -56,9 +54,7 @@ <h5 class="card-header">Data File</h5>
<th scope="row">SHA256 Hash</th>
<td>
<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">
<i class="mdi mdi-content-copy"></i>
</a>
{% copy_content "datafile_hash" %}
</td>
</tr>
</table>
Expand Down
6 changes: 4 additions & 2 deletions netbox/templates/dcim/device.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,13 @@ <h5 class="card-header">Management</h5>
<th scope="row">Primary IPv4</th>
<td>
{% 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 %}
(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 %}
(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 %}
{% copy_content "primary_ip4" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
Expand All @@ -209,12 +210,13 @@ <h5 class="card-header">Management</h5>
<th scope="row">Primary IPv6</th>
<td>
{% 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 %}
(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 %}
(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 %}
{% copy_content "primary_ip6" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
Expand Down
14 changes: 12 additions & 2 deletions netbox/templates/dcim/virtualdevicecontext.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@ <h5 class="card-header">
<tr>
<th scope="row">Primary IPv4</th>
<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>
</tr>
<tr>
<th scope="row">Primary IPv6</th>
<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>
</tr>
<tr>
Expand Down
6 changes: 2 additions & 4 deletions netbox/templates/users/api_token.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="col col-md-12">
{% if not settings.ALLOW_TOKEN_RETRIEVAL %}
<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>
{% endif %}
<div class="card">
Expand All @@ -19,9 +19,7 @@ <h5 class="card-header">Token</h5>
<th scope="row">Key</th>
<td>
<div class="float-end">
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_id" title="Copy to clipboard">
<i class="mdi mdi-content-copy"></i>
</a>
{% copy_content "token_id" %}
</div>
<div id="token_id">{{ key }}</div>
</td>
Expand Down
6 changes: 4 additions & 2 deletions netbox/templates/virtualization/virtualmachine.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ <h5 class="card-header">
<th scope="row">Primary IPv4</th>
<td>
{% 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 %}
(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 %}
(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 %}
{% copy_content "primary_ip4" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
Expand All @@ -61,12 +62,13 @@ <h5 class="card-header">
<th scope="row">Primary IPv6</th>
<td>
{% 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 %}
(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 %}
(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 %}
{% copy_content "primary_ip6" %}
{% else %}
{{ ''|placeholder }}
{% endif %}
Expand Down
4 changes: 1 addition & 3 deletions netbox/users/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@

COPY_BUTTON = """
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
<a class="btn btn-sm btn-success copy-token" data-clipboard-target="#token_{{ record.pk }}" title="Copy to clipboard">
<i class="mdi mdi-content-copy"></i>
</a>
{% copy_content record.pk prefix="token_" color="success" %}
{% endif %}
"""

Expand Down
3 changes: 3 additions & 0 deletions netbox/utilities/templates/builtins/copy_content.html
Original file line number Diff line number Diff line change
@@ -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>
12 changes: 12 additions & 0 deletions netbox/utilities/templatetags/builtins/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
__all__ = (
'badge',
'checkmark',
'copy_content',
'customfield_value',
'tag',
)
Expand Down Expand Up @@ -77,6 +78,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)
def htmx_table(context, viewname, return_url=None, **kwargs):
"""
Expand Down