mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Merge branch 'develop' into 3872-limit-related-ips
This commit is contained in:
commit
03b10b6f73
65
docs/additional-features/napalm.md
Normal file
65
docs/additional-features/napalm.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# NAPALM
|
||||||
|
|
||||||
|
NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to fetch live data from devices and return it to a requester via its REST API.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
To enable the integration, the NAPALM library must be installed. See [installation steps](../../installation/2-netbox/#napalm-automation-optional) for more information.
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/dcim/devices/1/napalm/?method=get_environment
|
||||||
|
|
||||||
|
{
|
||||||
|
"get_environment": {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) are used for NAPALM authentication. They can be overridden for an individual API call through the `X-NAPALM-Username` and `X-NAPALM-Password` headers.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
|
||||||
|
-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json; indent=4" \
|
||||||
|
-H "X-NAPALM-Username: foo" \
|
||||||
|
-H "X-NAPALM-Password: bar"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method Support
|
||||||
|
|
||||||
|
The list of supported NAPALM methods depends on the [NAPALM driver](https://napalm.readthedocs.io/en/latest/support/index.html#general-support-matrix) configured for the platform of a device. NetBox only supports [get](https://napalm.readthedocs.io/en/latest/support/index.html#getters-support-matrix) methods.
|
||||||
|
|
||||||
|
## Multiple Methods
|
||||||
|
|
||||||
|
More than one method in an API call can be invoked by adding multiple `method` parameters. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/dcim/devices/1/napalm/?method=get_ntp_servers&method=get_ntp_peers
|
||||||
|
|
||||||
|
{
|
||||||
|
"get_ntp_servers": {
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"get_ntp_peers": {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optional Arguments
|
||||||
|
|
||||||
|
The behavior of NAPALM drivers can be adjusted according to the [optional arguments](https://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments). NetBox exposes those arguments using headers prefixed with `X-NAPALM-`.
|
||||||
|
|
||||||
|
|
||||||
|
For instance, the SSH port is changed to 2222 in this API call:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
|
||||||
|
-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Accept: application/json; indent=4" \
|
||||||
|
-H "X-NAPALM-port: 2222"
|
||||||
|
```
|
@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger
|
||||||
* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link
|
* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link
|
||||||
|
* [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers
|
||||||
|
* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses
|
||||||
|
* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces
|
||||||
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
|
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
|
||||||
|
* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
|
||||||
* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
|
* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
@ -13,7 +18,8 @@
|
|||||||
* [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses
|
* [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses
|
||||||
* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering
|
* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering
|
||||||
* [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names
|
* [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names
|
||||||
* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Limit number of related IPs
|
* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks
|
||||||
|
* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ pages:
|
|||||||
- Custom Scripts: 'additional-features/custom-scripts.md'
|
- Custom Scripts: 'additional-features/custom-scripts.md'
|
||||||
- Export Templates: 'additional-features/export-templates.md'
|
- Export Templates: 'additional-features/export-templates.md'
|
||||||
- Graphs: 'additional-features/graphs.md'
|
- Graphs: 'additional-features/graphs.md'
|
||||||
|
- NAPALM: 'additional-features/napalm.md'
|
||||||
- Prometheus Metrics: 'additional-features/prometheus-metrics.md'
|
- Prometheus Metrics: 'additional-features/prometheus-metrics.md'
|
||||||
- Reports: 'additional-features/reports.md'
|
- Reports: 'additional-features/reports.md'
|
||||||
- Tags: 'additional-features/tags.md'
|
- Tags: 'additional-features/tags.md'
|
||||||
|
@ -370,6 +370,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
|||||||
return obj.get_config_context()
|
return obj.get_config_context()
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceNAPALMSerializer(serializers.Serializer):
|
||||||
|
method = serializers.DictField()
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
cable = NestedCableSerializer(read_only=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
|
@ -358,6 +358,17 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
|
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
manual_parameters=[
|
||||||
|
Parameter(
|
||||||
|
name='method',
|
||||||
|
in_='query',
|
||||||
|
required=True,
|
||||||
|
type=openapi.TYPE_STRING
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={'200': serializers.DeviceNAPALMSerializer}
|
||||||
|
)
|
||||||
@action(detail=True, url_path='napalm')
|
@action(detail=True, url_path='napalm')
|
||||||
def napalm(self, request, pk):
|
def napalm(self, request, pk):
|
||||||
"""
|
"""
|
||||||
@ -396,13 +407,29 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
napalm_methods = request.GET.getlist('method')
|
napalm_methods = request.GET.getlist('method')
|
||||||
response = OrderedDict([(m, None) for m in napalm_methods])
|
response = OrderedDict([(m, None) for m in napalm_methods])
|
||||||
ip_address = str(device.primary_ip.address.ip)
|
ip_address = str(device.primary_ip.address.ip)
|
||||||
|
username = settings.NAPALM_USERNAME
|
||||||
|
password = settings.NAPALM_PASSWORD
|
||||||
optional_args = settings.NAPALM_ARGS.copy()
|
optional_args = settings.NAPALM_ARGS.copy()
|
||||||
if device.platform.napalm_args is not None:
|
if device.platform.napalm_args is not None:
|
||||||
optional_args.update(device.platform.napalm_args)
|
optional_args.update(device.platform.napalm_args)
|
||||||
|
|
||||||
|
# Update NAPALM parameters according to the request headers
|
||||||
|
for header in request.headers:
|
||||||
|
if header[:9].lower() != 'x-napalm-':
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = header[9:]
|
||||||
|
if key.lower() == 'username':
|
||||||
|
username = request.headers[header]
|
||||||
|
elif key.lower() == 'password':
|
||||||
|
password = request.headers[header]
|
||||||
|
elif key:
|
||||||
|
optional_args[key.lower()] = request.headers[header]
|
||||||
|
|
||||||
d = driver(
|
d = driver(
|
||||||
hostname=ip_address,
|
hostname=ip_address,
|
||||||
username=settings.NAPALM_USERNAME,
|
username=username,
|
||||||
password=settings.NAPALM_PASSWORD,
|
password=password,
|
||||||
timeout=settings.NAPALM_TIMEOUT,
|
timeout=settings.NAPALM_TIMEOUT,
|
||||||
optional_args=optional_args
|
optional_args=optional_args
|
||||||
)
|
)
|
||||||
|
@ -2950,6 +2950,8 @@ class Cable(ChangeLoggedModel):
|
|||||||
# Store the given length (if any) in meters for use in database ordering
|
# Store the given length (if any) in meters for use in database ordering
|
||||||
if self.length and self.length_unit:
|
if self.length and self.length_unit:
|
||||||
self._abs_length = to_meters(self.length, self.length_unit)
|
self._abs_length = to_meters(self.length, self.length_unit)
|
||||||
|
else:
|
||||||
|
self._abs_length = None
|
||||||
|
|
||||||
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
# Store the parent Device for the A and B terminations (if applicable) to enable filtering
|
||||||
if hasattr(self.termination_a, 'device'):
|
if hasattr(self.termination_a, 'device'):
|
||||||
|
@ -1754,10 +1754,13 @@ class CableTraceView(PermissionRequiredMixin, View):
|
|||||||
def get(self, request, model, pk):
|
def get(self, request, model, pk):
|
||||||
|
|
||||||
obj = get_object_or_404(model, pk=pk)
|
obj = get_object_or_404(model, pk=pk)
|
||||||
|
trace = obj.trace(follow_circuits=True)
|
||||||
|
total_length = sum([entry[1]._abs_length for entry in trace if entry[1] and entry[1]._abs_length])
|
||||||
|
|
||||||
return render(request, 'dcim/cable_trace.html', {
|
return render(request, 'dcim/cable_trace.html', {
|
||||||
'obj': obj,
|
'obj': obj,
|
||||||
'trace': obj.trace(follow_circuits=True),
|
'trace': trace,
|
||||||
|
'total_length': total_length,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,6 +177,12 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
|
|||||||
# Clear host bits from prefix
|
# Clear host bits from prefix
|
||||||
self.prefix = self.prefix.cidr
|
self.prefix = self.prefix.cidr
|
||||||
|
|
||||||
|
# /0 masks are not acceptable
|
||||||
|
if self.prefix.prefixlen == 0:
|
||||||
|
raise ValidationError({
|
||||||
|
'prefix': "Cannot create aggregate with /0 mask."
|
||||||
|
})
|
||||||
|
|
||||||
# Ensure that the aggregate being added is not covered by an existing aggregate
|
# Ensure that the aggregate being added is not covered by an existing aggregate
|
||||||
covering_aggregates = Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix))
|
covering_aggregates = Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix))
|
||||||
if self.pk:
|
if self.pk:
|
||||||
@ -347,6 +353,12 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
if self.prefix:
|
if self.prefix:
|
||||||
|
|
||||||
|
# /0 masks are not acceptable
|
||||||
|
if self.prefix.prefixlen == 0:
|
||||||
|
raise ValidationError({
|
||||||
|
'prefix': "Cannot create prefix with /0 mask."
|
||||||
|
})
|
||||||
|
|
||||||
# Disallow host masks
|
# Disallow host masks
|
||||||
if self.prefix.version == 4 and self.prefix.prefixlen == 32:
|
if self.prefix.version == 4 and self.prefix.prefixlen == 32:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
@ -622,6 +634,12 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
|
|||||||
|
|
||||||
if self.address:
|
if self.address:
|
||||||
|
|
||||||
|
# /0 masks are not acceptable
|
||||||
|
if self.address.prefixlen == 0:
|
||||||
|
raise ValidationError({
|
||||||
|
'address': "Cannot create IP address with /0 mask."
|
||||||
|
})
|
||||||
|
|
||||||
# Enforce unique IP space (if applicable)
|
# Enforce unique IP space (if applicable)
|
||||||
if self.role not in IPADDRESS_ROLES_NONUNIQUE and ((
|
if self.role not in IPADDRESS_ROLES_NONUNIQUE and ((
|
||||||
self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE
|
self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE
|
||||||
|
@ -333,7 +333,10 @@ class AggregateView(PermissionRequiredMixin, View):
|
|||||||
).annotate_depth(
|
).annotate_depth(
|
||||||
limit=0
|
limit=0
|
||||||
)
|
)
|
||||||
child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
|
|
||||||
|
# Add available prefixes to the table if requested
|
||||||
|
if request.GET.get('show_available', 'true') == 'true':
|
||||||
|
child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
|
||||||
|
|
||||||
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
|
||||||
@ -356,6 +359,7 @@ class AggregateView(PermissionRequiredMixin, View):
|
|||||||
'aggregate': aggregate,
|
'aggregate': aggregate,
|
||||||
'prefix_table': prefix_table,
|
'prefix_table': prefix_table,
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -511,8 +515,8 @@ class PrefixPrefixesView(PermissionRequiredMixin, View):
|
|||||||
'site', 'vlan', 'role',
|
'site', 'vlan', 'role',
|
||||||
).annotate_depth(limit=0)
|
).annotate_depth(limit=0)
|
||||||
|
|
||||||
# Annotate available prefixes
|
# Add available prefixes to the table if requested
|
||||||
if child_prefixes:
|
if child_prefixes and request.GET.get('show_available', 'true') == 'true':
|
||||||
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
|
||||||
|
|
||||||
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
prefix_table = tables.PrefixDetailTable(child_prefixes)
|
||||||
@ -539,6 +543,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View):
|
|||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
'active_tab': 'prefixes',
|
'active_tab': 'prefixes',
|
||||||
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -553,7 +558,10 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View):
|
|||||||
ipaddresses = prefix.get_child_ips().prefetch_related(
|
ipaddresses = prefix.get_child_ips().prefetch_related(
|
||||||
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
|
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
|
||||||
)
|
)
|
||||||
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
|
||||||
|
# Add available IP addresses to the table if requested
|
||||||
|
if request.GET.get('show_available', 'true') == 'true':
|
||||||
|
ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
|
||||||
|
|
||||||
ip_table = tables.IPAddressTable(ipaddresses)
|
ip_table = tables.IPAddressTable(ipaddresses)
|
||||||
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
||||||
@ -579,6 +587,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View):
|
|||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
|
||||||
'active_tab': 'ip-addresses',
|
'active_tab': 'ip-addresses',
|
||||||
|
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// "Toggle" checkbox for object lists (PK column)
|
// "Toggle" checkbox for object lists (PK column)
|
||||||
$('input:checkbox.toggle').click(function() {
|
$('input:checkbox.toggle').click(function() {
|
||||||
$(this).closest('table').find('input:checkbox[name=pk]').prop('checked', $(this).prop('checked'));
|
$(this).closest('table').find('input:checkbox[name=pk]:visible').prop('checked', $(this).prop('checked'));
|
||||||
|
|
||||||
// Show the "select all" box if present
|
// Show the "select all" box if present
|
||||||
if ($(this).is(':checked')) {
|
if ($(this).is(':checked')) {
|
||||||
@ -400,8 +400,8 @@ $(document).ready(function() {
|
|||||||
window.addEventListener('hashchange', headerOffsetScroll);
|
window.addEventListener('hashchange', headerOffsetScroll);
|
||||||
|
|
||||||
// Offset between the preview window and the window edges
|
// Offset between the preview window and the window edges
|
||||||
const IMAGE_PREVIEW_OFFSET_X = 20
|
const IMAGE_PREVIEW_OFFSET_X = 20;
|
||||||
const IMAGE_PREVIEW_OFFSET_Y = 10
|
const IMAGE_PREVIEW_OFFSET_Y = 10;
|
||||||
|
|
||||||
// Preview an image attachment when the link is hovered over
|
// Preview an image attachment when the link is hovered over
|
||||||
$('a.image-preview').on('mouseover', function(e) {
|
$('a.image-preview').on('mouseover', function(e) {
|
||||||
@ -435,6 +435,6 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// Fade the image out; it will be deleted when another one is previewed
|
// Fade the image out; it will be deleted when another one is previewed
|
||||||
$('a.image-preview').on('mouseout', function() {
|
$('a.image-preview').on('mouseout', function() {
|
||||||
$('#image-preview-window').fadeOut('fast')
|
$('#image-preview-window').fadeOut('fast');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
30
netbox/project-static/js/interface_toggles.js
Normal file
30
netbox/project-static/js/interface_toggles.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Toggle the display of IP addresses under interfaces
|
||||||
|
$('button.toggle-ips').click(function() {
|
||||||
|
var selected = $(this).attr('selected');
|
||||||
|
if (selected) {
|
||||||
|
$('#interfaces_table tr.ipaddresses').hide();
|
||||||
|
} else {
|
||||||
|
$('#interfaces_table tr.ipaddresses').show();
|
||||||
|
}
|
||||||
|
$(this).attr('selected', !selected);
|
||||||
|
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inteface filtering
|
||||||
|
$('input.interface-filter').on('input', function() {
|
||||||
|
var filter = new RegExp(this.value);
|
||||||
|
|
||||||
|
for (interface of $(this).closest('form').find('tbody > tr')) {
|
||||||
|
// Slice off 'interface_' at the start of the ID
|
||||||
|
if (filter && filter.test(interface.id.slice(10))) {
|
||||||
|
// Match the toggle in case the filter now matches the interface
|
||||||
|
$(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked'));
|
||||||
|
$(interface).show();
|
||||||
|
} else {
|
||||||
|
// Uncheck to prevent actions from including it when it doesn't match
|
||||||
|
$(interface).find('input:checkbox[name=pk]').prop('checked', false);
|
||||||
|
$(interface).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -10,7 +10,10 @@
|
|||||||
<div class="col-md-4 col-md-offset-1 text-center">
|
<div class="col-md-4 col-md-offset-1 text-center">
|
||||||
<h4>Near End</h4>
|
<h4>Near End</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-md-offset-3 text-center">
|
<div class="col-md-3 text-center">
|
||||||
|
{% if total_length %}<h5>Total length: {{ total_length|floatformat:"-2" }} Meters<h5>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
<h4>Far End</h4>
|
<h4>Far End</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -556,6 +556,9 @@
|
|||||||
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-2 pull-right noprint">
|
||||||
|
<input class="form-control interface-filter" type="text" placeholder="Filter" title="RegEx-enabled" style="height: 23px" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
|
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
|
||||||
<thead>
|
<thead>
|
||||||
@ -900,19 +903,8 @@ function toggleConnection(elem) {
|
|||||||
$(".cable-toggle").click(function() {
|
$(".cable-toggle").click(function() {
|
||||||
return toggleConnection($(this));
|
return toggleConnection($(this));
|
||||||
});
|
});
|
||||||
// Toggle the display of IP addresses under interfaces
|
|
||||||
$('button.toggle-ips').click(function() {
|
|
||||||
var selected = $(this).attr('selected');
|
|
||||||
if (selected) {
|
|
||||||
$('#interfaces_table tr.ipaddresses').hide();
|
|
||||||
} else {
|
|
||||||
$('#interfaces_table tr.ipaddresses').show();
|
|
||||||
}
|
|
||||||
$(this).attr('selected', !selected);
|
|
||||||
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="{% static 'js/interface_toggles.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
<script src="{% static 'js/graphs.js' %}?v{{ settings.VERSION }}"></script>
|
<script src="{% static 'js/graphs.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
<script src="{% static 'js/secrets.js' %}?v{{ settings.VERSION }}"></script>
|
<script src="{% static 'js/secrets.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ aggregate }}{% endblock %}</h1>
|
<h1>{% block title %}{{ aggregate }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=aggregate %}
|
{% include 'inc/created_updated.html' with obj=aggregate %}
|
||||||
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links aggregate %}
|
{% custom_links aggregate %}
|
||||||
</div>
|
</div>
|
||||||
|
9
netbox/templates/ipam/inc/toggle_available.html
Normal file
9
netbox/templates/ipam/inc/toggle_available.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% load helpers %}
|
||||||
|
{% if show_available is not None %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<a href="{{ request.path }}{% querystring request show_available='true' %}" class="btn btn-default{% if show_available %} active disabled{% endif %}">Show available</a>
|
||||||
|
<a href="{{ request.path }}{% querystring request show_available='false' %}" class="btn btn-default{% if not show_available %} active disabled{% endif %}">Hide available</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
@ -53,6 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h1>{% block title %}{{ prefix }}{% endblock %}</h1>
|
<h1>{% block title %}{{ prefix }}{% endblock %}</h1>
|
||||||
{% include 'inc/created_updated.html' with obj=prefix %}
|
{% include 'inc/created_updated.html' with obj=prefix %}
|
||||||
|
{% include 'ipam/inc/toggle_available.html' %}
|
||||||
<div class="pull-right noprint">
|
<div class="pull-right noprint">
|
||||||
{% custom_links prefix %}
|
{% custom_links prefix %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends '_base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load custom_links %}
|
{% load custom_links %}
|
||||||
|
{% load static %}
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
@ -253,6 +254,9 @@
|
|||||||
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
<span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-2 pull-right noprint">
|
||||||
|
<input class="form-control interface-filter" type="text" placeholder="Filter" title="RegEx-enabled" style="height: 23px" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
|
<table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
|
||||||
<thead>
|
<thead>
|
||||||
@ -312,18 +316,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
<script type="text/javascript">
|
<script src="{% static 'js/interface_toggles.js' %}?v{{ settings.VERSION }}"></script>
|
||||||
// Toggle the display of IP addresses under interfaces
|
|
||||||
$('button.toggle-ips').click(function() {
|
|
||||||
var selected = $(this).attr('selected');
|
|
||||||
if (selected) {
|
|
||||||
$('#interfaces_table tr.ipaddresses').hide();
|
|
||||||
} else {
|
|
||||||
$('#interfaces_table tr.ipaddresses').show();
|
|
||||||
}
|
|
||||||
$(this).attr('selected', !selected);
|
|
||||||
$(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user