diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 86d1a3775..aff090f3a 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -19,14 +19,22 @@ __all__ = ( AVAILABLE_LABEL = mark_safe('Available') +AGGREGATE_COPY_BUTTON = """ +{% copy_content record.pk prefix="aggregate_" %} +""" + PREFIX_LINK = """ {% if record.pk %} - {{ record.prefix }} + {{ record.prefix }} {% else %} {{ record.prefix }} {% endif %} """ +PREFIX_COPY_BUTTON = """ +{% copy_content record.pk prefix="prefix_" %} +""" + PREFIX_LINK_WITH_DEPTH = """ {% load helpers %} {% if record.depth %} @@ -40,7 +48,7 @@ PREFIX_LINK_WITH_DEPTH = """ IPADDRESS_LINK = """ {% if record.pk %} - {{ record.address }} + {{ record.address }} {% elif perms.ipam.add_ipaddress %} {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available {% else %} @@ -48,6 +56,10 @@ IPADDRESS_LINK = """ {% endif %} """ +IPADDRESS_COPY_BUTTON = """ +{% copy_content record.pk prefix="ipaddress_" %} +""" + IPADDRESS_ASSIGN_LINK = """ {{ record }} """ @@ -99,7 +111,11 @@ class RIRTable(NetBoxTable): 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", @@ -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 @@ -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 @@ -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 diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 9642d1585..b62436d75 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index f86d50148..ed3833f98 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/src/clipboard.ts b/netbox/project-static/src/clipboard.ts index a04acba39..46ca5e36c 100644 --- a/netbox/project-static/src/clipboard.ts +++ b/netbox/project-static/src/clipboard.ts @@ -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); } } diff --git a/netbox/templates/core/datafile.html b/netbox/templates/core/datafile.html index 3d79d17e2..785617ae5 100644 --- a/netbox/templates/core/datafile.html +++ b/netbox/templates/core/datafile.html @@ -39,9 +39,7 @@ Path {{ object.path }} - - - + {% copy_content "datafile_path" %} @@ -56,9 +54,7 @@ SHA256 Hash {{ object.hash }} - - - + {% copy_content "datafile_hash" %} diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index b0e67269c..df5209add 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -194,12 +194,13 @@ Primary IPv4 {% if object.primary_ip4 %} - {{ object.primary_ip4.address.ip }} + {{ object.primary_ip4.address.ip }} {% if object.primary_ip4.nat_inside %} (NAT for {{ object.primary_ip4.nat_inside.address.ip }}) {% elif object.primary_ip4.nat_outside.exists %} (NAT: {% for nat in object.primary_ip4.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip4" %} {% else %} {{ ''|placeholder }} {% endif %} @@ -209,12 +210,13 @@ Primary IPv6 {% if object.primary_ip6 %} - {{ object.primary_ip6.address.ip }} + {{ object.primary_ip6.address.ip }} {% if object.primary_ip6.nat_inside %} (NAT for {{ object.primary_ip6.nat_inside.address.ip }}) {% elif object.primary_ip6.nat_outside.exists %} (NAT: {% for nat in object.primary_ip6.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip6" %} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox/templates/dcim/virtualdevicecontext.html b/netbox/templates/dcim/virtualdevicecontext.html index d6e3e0c63..1caf05bd2 100644 --- a/netbox/templates/dcim/virtualdevicecontext.html +++ b/netbox/templates/dcim/virtualdevicecontext.html @@ -31,13 +31,23 @@ Primary IPv4 - {{ object.primary_ip4|linkify|placeholder }} + {% if object.primary_ip4 %} + {{ object.primary_ip4 }} + {% copy_content "primary_ip4" %} + {% else %} + + {% endif %} Primary IPv6 - {{ object.primary_ip6|linkify|placeholder }} + {% if object.primary_ip6 %} + {{ object.primary_ip6 }} + {% copy_content "primary_ip6" %} + {% else %} + + {% endif %} diff --git a/netbox/templates/users/api_token.html b/netbox/templates/users/api_token.html index 1a9296704..7fd6f064d 100644 --- a/netbox/templates/users/api_token.html +++ b/netbox/templates/users/api_token.html @@ -8,7 +8,7 @@
{% if not settings.ALLOW_TOKEN_RETRIEVAL %} {% endif %}
@@ -19,9 +19,7 @@ Key
- - - + {% copy_content "token_id" %}
{{ key }}
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 51fd8aa80..3d3b498ad 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -46,12 +46,13 @@ Primary IPv4 {% if object.primary_ip4 %} - {{ object.primary_ip4.address.ip }} + {{ object.primary_ip4.address.ip }} {% if object.primary_ip4.nat_inside %} (NAT for {{ object.primary_ip4.nat_inside.address.ip }}) {% elif object.primary_ip4.nat_outside.exists %} (NAT: {% for nat in object.primary_ip4.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip4" %} {% else %} {{ ''|placeholder }} {% endif %} @@ -61,12 +62,13 @@ Primary IPv6 {% if object.primary_ip6 %} - {{ object.primary_ip6.address.ip }} + {{ object.primary_ip6.address.ip }} {% if object.primary_ip6.nat_inside %} (NAT for {{ object.primary_ip6.nat_inside.address.ip }}) {% elif object.primary_ip6.nat_outside.exists %} (NAT: {% for nat in object.primary_ip6.nat_outside.all %}{{ nat.address.ip }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% endif %} + {% copy_content "primary_ip6" %} {% else %} {{ ''|placeholder }} {% endif %} diff --git a/netbox/users/tables.py b/netbox/users/tables.py index 0f1484887..cea50b10f 100644 --- a/netbox/users/tables.py +++ b/netbox/users/tables.py @@ -12,9 +12,7 @@ ALLOWED_IPS = """{{ value|join:", " }}""" COPY_BUTTON = """ {% if settings.ALLOW_TOKEN_RETRIEVAL %} - - - + {% copy_content record.pk prefix="token_" color="success" %} {% endif %} """ diff --git a/netbox/utilities/templates/builtins/copy_content.html b/netbox/utilities/templates/builtins/copy_content.html new file mode 100644 index 000000000..9025a71a1 --- /dev/null +++ b/netbox/utilities/templates/builtins/copy_content.html @@ -0,0 +1,3 @@ + + + diff --git a/netbox/utilities/templatetags/builtins/tags.py b/netbox/utilities/templatetags/builtins/tags.py index f9fe5f4e3..35aec1000 100644 --- a/netbox/utilities/templatetags/builtins/tags.py +++ b/netbox/utilities/templatetags/builtins/tags.py @@ -6,6 +6,7 @@ from utilities.utils import dict_to_querydict __all__ = ( 'badge', 'checkmark', + 'copy_content', 'customfield_value', '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) def htmx_table(context, viewname, return_url=None, **kwargs): """