diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0facf5f62..34d4b7f5d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,25 @@
+v2.6.5 (2019-09-25)
+
+## Enhancements
+
+* [#3297](https://github.com/netbox-community/netbox/issues/3297) - Include reserved units when calculating rack utilization
+* [#3347](https://github.com/netbox-community/netbox/issues/3347) - Extend upgrade script to automatically remove stale content types
+* [#3352](https://github.com/netbox-community/netbox/issues/3352) - Enable filtering changelog API by `changed_object_id`
+* [#3515](https://github.com/netbox-community/netbox/issues/3515) - Enable export templates for inventory items
+* [#3524](https://github.com/netbox-community/netbox/issues/3524) - Enable bulk editing of power outlet/power port associations
+* [#3529](https://github.com/netbox-community/netbox/issues/3529) - Enable filtering circuits list by region
+
+## Bug Fixes
+
+* [#3435](https://github.com/netbox-community/netbox/issues/3435) - Change IP/prefix CSV export to reference VRF name instead of RD
+* [#3464](https://github.com/netbox-community/netbox/issues/3464) - Fix foreground text color on color picker fields
+* [#3519](https://github.com/netbox-community/netbox/issues/3519) - Prevent cables from being terminated to virtual/wireless interfaces via API
+* [#3521](https://github.com/netbox-community/netbox/issues/3521) - Fix error in `parseURL` related to variables in API URL
+* [#3531](https://github.com/netbox-community/netbox/issues/3531) - Fixed rack role foreground color
+* [#3534](https://github.com/netbox-community/netbox/issues/3534) - Added blank option for untagged VLANs
+* [#3540](https://github.com/netbox-community/netbox/issues/3540) - Fixed virtual machine interface edit with new inline vlan edit fields
+* [#3543](https://github.com/netbox-community/netbox/issues/3543) - Added inline VLAN editing to virtual machine interfaces
+
v2.6.4 (2019-09-19)
## Enhancements
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f27317deb..a688be9b3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -117,3 +117,25 @@ Only comment on an issue if you are sharing a relevant idea or constructive
feedback. **Do not** comment on an issue just to show your support (give the
top post a :+1: instead) or ask for an ETA. These comments will be deleted to
reduce noise in the discussion.
+
+## Maintainer Guidance
+
+* Maintainers are expected to contribute at least four hours per week to the
+ project on average. This can be employer-sponsored or individual time, with
+ the understanding that all contributions are submitted under the Apache 2.0
+ license and that your employer may not make claim to any contributions.
+ Contributions include code work, issue management, and community support. All
+ development must be in accordance with our [development guidance](https://netbox.readthedocs.io/en/stable/development/).
+
+* Maintainers are expected to attend (where feasible) our biweekly ~30-minute
+ sync to review agenda items. This meeting provides opportunity to present and
+ discuss pressing topics. Meetings are held as virtual audio/video conferences.
+
+* Official channels for communication include:
+
+ * GitHub issues/pull requests
+ * The [netbox-discuss](https://groups.google.com/forum/#!forum/netbox-discuss) mailing list
+ * The **#netbox** channel on [NetworkToCode Slack](https://networktocode.slack.com/)
+
+* Maintainers with no substantial recorded activity in a 60-day period will be
+ removed from the project.
diff --git a/README.md b/README.md
index dc673221d..617268c69 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ and run `upgrade.sh`.
* [Docker container](https://github.com/netbox-community/netbox-docker) (via [@cimnine](https://github.com/cimnine))
* [Vagrant deployment](https://github.com/ryanmerolle/netbox-vagrant) (via [@ryanmerolle](https://github.com/ryanmerolle))
* [Ansible deployment](https://github.com/lae/ansible-role-netbox) (via [@lae](https://github.com/lae))
+* [Kubernetes deployment](https://github.com/CENGN/netbox-kubernetes) (via [@CENGN](https://github.com/CENGN))
# Related projects
diff --git a/docs/additional-features/graphs.md b/docs/additional-features/graphs.md
index 7b37276e8..b20a6b424 100644
--- a/docs/additional-features/graphs.md
+++ b/docs/additional-features/graphs.md
@@ -2,7 +2,7 @@
NetBox does not have the ability to generate graphs natively, but this feature allows you to embed contextual graphs from an external resources (such as a monitoring system) inside the site, provider, and interface views. Each embedded graph must be defined with the following parameters:
-* **Type:** Site, provider, or interface. This determines in which view the graph will be displayed.
+* **Type:** Site, device, provider, or interface. This determines in which view the graph will be displayed.
* **Weight:** Determines the order in which graphs are displayed (lower weights are displayed first). Graphs with equal weights will be ordered alphabetically by name.
* **Name:** The title to display above the graph.
* **Source URL:** The source of the image to be embedded. The associated object will be available as a template variable named `obj`.
diff --git a/mkdocs.yml b/mkdocs.yml
index 99f77d06c..932536d66 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -27,17 +27,18 @@ pages:
- Secrets: 'core-functionality/secrets.md'
- Tenancy: 'core-functionality/tenancy.md'
- Additional Features:
- - Tags: 'additional-features/tags.md'
- - Custom Fields: 'additional-features/custom-fields.md'
+ - Caching: 'additional-features/caching.md'
+ - Change Logging: 'additional-features/change-logging.md'
- Context Data: 'additional-features/context-data.md'
+ - Custom Fields: 'additional-features/custom-fields.md'
+ - Custom Scripts: 'additional-features/custom-scripts.md'
- Export Templates: 'additional-features/export-templates.md'
- Graphs: 'additional-features/graphs.md'
- - Topology Maps: 'additional-features/topology-maps.md'
- - Reports: 'additional-features/reports.md'
- - Webhooks: 'additional-features/webhooks.md'
- - Change Logging: 'additional-features/change-logging.md'
- - Caching: 'additional-features/caching.md'
- Prometheus Metrics: 'additional-features/prometheus-metrics.md'
+ - Reports: 'additional-features/reports.md'
+ - Tags: 'additional-features/tags.md'
+ - Topology Maps: 'additional-features/topology-maps.md'
+ - Webhooks: 'additional-features/webhooks.md'
- Administration:
- Replicating NetBox: 'administration/replicating-netbox.md'
- NetBox Shell: 'administration/netbox-shell.md'
diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py
index 100c6334f..a64dfc11e 100644
--- a/netbox/circuits/forms.py
+++ b/netbox/circuits/forms.py
@@ -1,7 +1,7 @@
from django import forms
from taggit.forms import TagField
-from dcim.models import Site
+from dcim.models import Region, Site
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from tenancy.forms import TenancyForm
from tenancy.forms import TenancyFilterForm
@@ -268,7 +268,9 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
model = Circuit
- field_order = ['q', 'type', 'provider', 'status', 'site', 'tenant_group', 'tenant', 'commit_rate']
+ field_order = [
+ 'q', 'type', 'provider', 'status', 'region', 'site', 'tenant_group', 'tenant', 'commit_rate',
+ ]
q = forms.CharField(
required=False,
label='Search'
@@ -294,6 +296,15 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
required=False,
widget=StaticSelect2Multiple()
)
+ region = forms.ModelMultipleChoiceField(
+ queryset=Region.objects.all(),
+ to_field_name='slug',
+ required=False,
+ widget=APISelectMultiple(
+ api_url="/api/dcim/regions/",
+ value_field="slug",
+ )
+ )
site = FilterChoiceField(
queryset=Site.objects.all(),
to_field_name='slug',
diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py
index 6c145e9fa..c2818e267 100644
--- a/netbox/dcim/forms.py
+++ b/netbox/dcim/forms.py
@@ -2228,6 +2228,10 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
choices=add_blank_choice(POWERFEED_LEG_CHOICES),
required=False,
)
+ power_port = forms.ModelChoiceField(
+ queryset=PowerPort.objects.all(),
+ required=False
+ )
description = forms.CharField(
max_length=100,
required=False
@@ -2235,9 +2239,15 @@ class PowerOutletBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditForm):
class Meta:
nullable_fields = [
- 'feed_leg', 'description',
+ 'feed_leg', 'power_port', 'description',
]
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Limit power_port queryset to PowerPorts which belong to the parent Device
+ self.fields['power_port'].queryset = PowerPort.objects.filter(device=self.parent_obj)
+
class PowerOutletBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(
@@ -2342,7 +2352,7 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
[(vlan.pk, vlan) for vlan in site_group_vlans]
))
- self.fields['untagged_vlan'].choices = vlan_choices
+ self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
self.fields['tagged_vlans'].choices = vlan_choices
@@ -2452,7 +2462,7 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
[(vlan.pk, vlan) for vlan in site_group_vlans]
))
- self.fields['untagged_vlan'].choices = vlan_choices
+ self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
self.fields['tagged_vlans'].choices = vlan_choices
@@ -2564,7 +2574,7 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
[(vlan.pk, vlan) for vlan in site_group_vlans]
))
- self.fields['untagged_vlan'].choices = vlan_choices
+ self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
self.fields['tagged_vlans'].choices = vlan_choices
diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py
index 88d2aee1d..5ffd15842 100644
--- a/netbox/dcim/models.py
+++ b/netbox/dcim/models.py
@@ -732,10 +732,21 @@ class Rack(ChangeLoggedModel, CustomFieldModel):
def get_utilization(self):
"""
- Determine the utilization rate of the rack and return it as a percentage.
+ Determine the utilization rate of the rack and return it as a percentage. Occupied and reserved units both count
+ as utilized.
"""
- u_available = len(self.get_available_units())
- return int(float(self.u_height - u_available) / self.u_height * 100)
+ # Determine unoccupied units
+ available_units = self.get_available_units()
+
+ # Remove reserved units
+ for u in self.get_reserved_units():
+ if u in available_units:
+ available_units.remove(u)
+
+ occupied_unit_count = self.u_height - len(available_units)
+ percentage = int(float(occupied_unit_count) / self.u_height * 100)
+
+ return percentage
def get_power_utilization(self):
"""
@@ -2817,6 +2828,20 @@ class Cable(ChangeLoggedModel):
type_a = self.termination_a_type.model
type_b = self.termination_b_type.model
+ # Validate interface types
+ if type_a == 'interface' and self.termination_a.type in NONCONNECTABLE_IFACE_TYPES:
+ raise ValidationError({
+ 'termination_a_id': 'Cables cannot be terminated to {} interfaces'.format(
+ self.termination_a.get_type_display()
+ )
+ })
+ if type_b == 'interface' and self.termination_b.type in NONCONNECTABLE_IFACE_TYPES:
+ raise ValidationError({
+ 'termination_b_id': 'Cables cannot be terminated to {} interfaces'.format(
+ self.termination_b.get_type_display()
+ )
+ })
+
# Check that termination types are compatible
if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
raise ValidationError("Incompatible termination types: {} and {}".format(
@@ -2858,20 +2883,6 @@ class Cable(ChangeLoggedModel):
self.termination_b, self.termination_b.cable_id
))
- # Virtual interfaces cannot be connected
- endpoint_a, endpoint_b, _ = self.get_path_endpoints()
- if (
- (
- isinstance(endpoint_a, Interface) and
- endpoint_a.type == IFACE_TYPE_VIRTUAL
- ) or
- (
- isinstance(endpoint_b, Interface) and
- endpoint_b.type == IFACE_TYPE_VIRTUAL
- )
- ):
- raise ValidationError("Cannot connect to a virtual interface")
-
# Validate length and length_unit
if self.length is not None and self.length_unit is None:
raise ValidationError("Must specify a unit when setting a cable length")
diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py
index 250173d79..ae42c507e 100644
--- a/netbox/dcim/tables.py
+++ b/netbox/dcim/tables.py
@@ -74,7 +74,8 @@ RACKROLE_ACTIONS = """
RACK_ROLE = """
{% if record.role %}
-
+ {% load helpers %}
+
{% else %}
—
{% endif %}
diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py
index 2135aba66..2b5bed283 100644
--- a/netbox/dcim/tests/test_models.py
+++ b/netbox/dcim/tests/test_models.py
@@ -343,7 +343,7 @@ class CableTestCase(TestCase):
def test_cable_validates_compatibale_types(self):
"""
- The clean method should have a check to ensure only compatiable port types can be connected by a cable
+ The clean method should have a check to ensure only compatible port types can be connected by a cable
"""
# An interface cannot be connected to a power port
cable = Cable(termination_a=self.interface1, termination_b=self.power_port1)
@@ -360,30 +360,39 @@ class CableTestCase(TestCase):
def test_cable_front_port_cannot_connect_to_corresponding_rear_port(self):
"""
- A cable cannot connect a front port to its sorresponding rear port
+ A cable cannot connect a front port to its corresponding rear port
"""
cable = Cable(termination_a=self.front_port, termination_b=self.rear_port)
with self.assertRaises(ValidationError):
cable.clean()
- def test_cable_cannot_be_connected_to_an_existing_connection(self):
+ def test_cable_cannot_terminate_to_an_existing_connection(self):
"""
- Either side of a cable cannot be terminated when that side aready has a connection
+ Either side of a cable cannot be terminated when that side already has a connection
"""
# Try to create a cable with the same interface terminations
cable = Cable(termination_a=self.interface2, termination_b=self.interface1)
with self.assertRaises(ValidationError):
cable.clean()
- def test_cable_cannot_connect_to_a_virtual_inteface(self):
+ def test_cable_cannot_terminate_to_a_virtual_inteface(self):
"""
- A cable connection cannot include a virtual interface
+ A cable cannot terminate to a virtual interface
"""
- virtual_interface = Interface(device=self.device1, name="V1", type=0)
+ virtual_interface = Interface(device=self.device1, name="V1", type=IFACE_TYPE_VIRTUAL)
cable = Cable(termination_a=self.interface2, termination_b=virtual_interface)
with self.assertRaises(ValidationError):
cable.clean()
+ def test_cable_cannot_terminate_to_a_wireless_inteface(self):
+ """
+ A cable cannot terminate to a wireless interface
+ """
+ wireless_interface = Interface(device=self.device1, name="W1", type=IFACE_TYPE_80211A)
+ cable = Cable(termination_a=self.interface2, termination_b=wireless_interface)
+ with self.assertRaises(ValidationError):
+ cable.clean()
+
class CablePathTestCase(TestCase):
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index abf0d8cf5..8cbddc860 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -235,8 +235,8 @@ class ObjectChangeSerializer(serializers.ModelSerializer):
class Meta:
model = ObjectChange
fields = [
- 'id', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object',
- 'object_data',
+ 'id', 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object_id',
+ 'changed_object', 'object_data',
]
@swagger_serializer_method(serializer_or_field=serializers.DictField)
diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py
index d136d3271..2545b34d5 100644
--- a/netbox/extras/constants.py
+++ b/netbox/extras/constants.py
@@ -107,6 +107,7 @@ EXPORTTEMPLATE_MODELS = [
'dcim.device',
'dcim.devicetype',
'dcim.interface',
+ 'dcim.inventoryitem',
'dcim.manufacturer',
'dcim.powerpanel',
'dcim.powerport',
diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py
index b31271230..66a7fe637 100644
--- a/netbox/extras/filters.py
+++ b/netbox/extras/filters.py
@@ -230,7 +230,9 @@ class ObjectChangeFilter(django_filters.FilterSet):
class Meta:
model = ObjectChange
- fields = ['user', 'user_name', 'request_id', 'action', 'changed_object_type', 'object_repr']
+ fields = [
+ 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object_id', 'object_repr',
+ ]
def search(self, queryset, name, value):
if not value.strip():
diff --git a/netbox/extras/models.py b/netbox/extras/models.py
index d764e3d31..04caf3376 100644
--- a/netbox/extras/models.py
+++ b/netbox/extras/models.py
@@ -435,14 +435,19 @@ class ExportTemplate(models.Model):
choices=TEMPLATE_LANGUAGE_CHOICES,
default=TEMPLATE_LANGUAGE_JINJA2
)
- template_code = models.TextField()
+ template_code = models.TextField(
+ help_text='The list of objects being exported is passed as a context variable named queryset
.'
+ )
mime_type = models.CharField(
max_length=50,
- blank=True
+ blank=True,
+ verbose_name='MIME type',
+ help_text='Defaults to text/plain
'
)
file_extension = models.CharField(
max_length=15,
- blank=True
+ blank=True,
+ help_text='Extension to append to the rendered filename'
)
class Meta:
diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py
index 5e867e1d6..3304c27dd 100644
--- a/netbox/ipam/models.py
+++ b/netbox/ipam/models.py
@@ -382,7 +382,7 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
def to_csv(self):
return (
self.prefix,
- self.vrf.rd if self.vrf else None,
+ self.vrf.name if self.vrf else None,
self.tenant.name if self.tenant else None,
self.site.name if self.site else None,
self.vlan.group.name if self.vlan and self.vlan.group else None,
@@ -674,7 +674,7 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
return (
self.address,
- self.vrf.rd if self.vrf else None,
+ self.vrf.name if self.vrf else None,
self.tenant.name if self.tenant else None,
self.get_status_display(),
self.get_role_display(),
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 2fbdc6ac7..0175d89f2 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
# Environment setup
#
-VERSION = '2.6.5-dev'
+VERSION = '2.6.6-dev'
# Hostname
HOSTNAME = platform.node()
diff --git a/netbox/project-static/css/base.css b/netbox/project-static/css/base.css
index 24cf9a9ea..d37a2106e 100644
--- a/netbox/project-static/css/base.css
+++ b/netbox/project-static/css/base.css
@@ -133,116 +133,6 @@ input[name="pk"] {
margin-top: 0;
}
-/* Color Selections */
-.color-selection-aa1409 {
- background-color: #aa1409;
- color: #ffffff;
-}
-.color-selection-f44336 {
- background-color: #f44336;
- color: #ffffff;
-}
-.color-selection-e91e63 {
- background-color: #e91e63;
- color: #ffffff;
-}
-.color-selection-ffe4e1 {
- background-color: #ffe4e1;
- color: #000000;
-}
-.color-selection-ff66ff {
- background-color: #ff66ff;
- color: #ffffff;
-}
-.color-selection-9c27b0 {
- background-color: #9c27b0;
- color: #ffffff;
-}
-.color-selection-673ab7 {
- background-color: #673ab7;
- color: #ffffff;
-}
-.color-selection-3f51b5 {
- background-color: #3f51b5;
- color: #ffffff;
-}
-.color-selection-2196f3 {
- background-color: #2196f3;
- color: #ffffff;
-}
-.color-selection-03a9f4 {
- background-color: #03a9f4;
- color: #ffffff;
-}
-.color-selection-00bcd4 {
- background-color: #00bcd4;
- color: #ffffff;
-}
-.color-selection-009688 {
- background-color: #009688;
- color: #ffffff;
-}
-.color-selection-00ffff {
- background-color: #00ffff;
- color: #ffffff;
-}
-.color-selection-2f6a31 {
- background-color: #2f6a31;
- color: #ffffff;
-}
-.color-selection-4caf50 {
- background-color: #4caf50;
- color: #ffffff;
-}
-.color-selection-8bc34a {
- background-color: #8bc34a;
- color: #ffffff;
-}
-.color-selection-cddc39 {
- background-color: #cddc39;
- color: #000000;
-}
-.color-selection-ffeb3b {
- background-color: #ffeb3b;
- color: #000000;
-}
-.color-selection-ffc107 {
- background-color: #ffc107;
- color: #000000;
-}
-.color-selection-ff9800 {
- background-color: #ff9800;
- color: #ffffff;
-}
-.color-selection-ff5722 {
- background-color: #ff5722;
- color: #ffffff;
-}
-.color-selection-795548 {
- background-color: #795548;
- color: #ffffff;
-}
-.color-selection-c0c0c0 {
- background-color: #c0c0c0;
- color: #000000;
-}
-.color-selection-9e9e9e {
- background-color: #9e9e9e;
- color: #ffffff;
-}
-.color-selection-607d8b {
- background-color: #607d8b;
- color: #ffffff;
-}
-.color-selection-111111 {
- background-color: #111111;
- color: #ffffff;
-}
-.color-selection-ffffff {
- background-color: #ffffff;
- color: #000000;
-}
-
/* Tables */
th.pk, td.pk {
diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js
index 287c19465..ae3501cae 100644
--- a/netbox/project-static/js/forms.js
+++ b/netbox/project-static/js/forms.js
@@ -75,7 +75,7 @@ $(document).ready(function() {
var rendered_url = url;
var filter_field;
while (match = filter_regex.exec(url)) {
- filter_field = $('#id_' + match[1]);untagged
+ filter_field = $('#id_' + match[1]);
var custom_attr = $('option:selected', filter_field).attr('api-value');
if (custom_attr) {
rendered_url = rendered_url.replace(match[0], custom_attr);
@@ -91,11 +91,8 @@ $(document).ready(function() {
// Assign color picker selection classes
function colorPickerClassCopy(data, container) {
if (data.element) {
- // Remove any existing color-selection classes
- $(container).attr('class', function(i, c) {
- return c.replace(/(^|\s)color-selection-\S+/g, '');
- });
- $(container).addClass($(data.element).attr("class"));
+ // Swap the style
+ $(container).attr('style', $(data.element).attr("style"));
}
return data.text;
}
@@ -200,7 +197,7 @@ $(document).ready(function() {
$(element).children('option').attr('disabled', false);
var results = data.results;
- results = results.reduce((results,record) => {
+ results = results.reduce((results,record,idx) => {
record.text = record[element.getAttribute('display-field')] || record.name;
record.id = record[element.getAttribute('value-field')] || record.id;
if(element.getAttribute('disabled-indicator') && record[element.getAttribute('disabled-indicator')]) {
@@ -225,7 +222,7 @@ $(document).ready(function() {
results['global'].children.push(record);
}
else {
- results[record.id] = record
+ results[idx] = record
}
return results;
diff --git a/netbox/scripts/examples.py b/netbox/scripts/examples.py
deleted file mode 100644
index b2adf8da4..000000000
--- a/netbox/scripts/examples.py
+++ /dev/null
@@ -1,66 +0,0 @@
-from django.utils.text import slugify
-
-from dcim.constants import *
-from dcim.models import Device, DeviceRole, DeviceType, Site
-from extras.scripts import *
-
-
-class NewBranchScript(Script):
- script_name = "New Branch"
- script_description = "Provision a new branch site"
- script_fields = ['site_name', 'switch_count', 'switch_model']
-
- site_name = StringVar(
- description="Name of the new site"
- )
- switch_count = IntegerVar(
- description="Number of access switches to create"
- )
- switch_model = ObjectVar(
- description="Access switch model",
- queryset=DeviceType.objects.filter(
- manufacturer__name='Cisco',
- model__in=['Catalyst 3560X-48T', 'Catalyst 3750X-48T']
- )
- )
- x = BooleanVar(
- description="Check me out"
- )
-
- def run(self, data):
-
- # Create the new site
- site = Site(
- name=data['site_name'],
- slug=slugify(data['site_name']),
- status=SITE_STATUS_PLANNED
- )
- site.save()
- self.log_success("Created new site: {}".format(site))
-
- # Create access switches
- switch_role = DeviceRole.objects.get(name='Access Switch')
- for i in range(1, data['switch_count'] + 1):
- switch = Device(
- device_type=data['switch_model'],
- name='{}-switch{}'.format(site.slug, i),
- site=site,
- status=DEVICE_STATUS_PLANNED,
- device_role=switch_role
- )
- switch.save()
- self.log_success("Created new switch: {}".format(switch))
-
- # Generate a CSV table of new devices
- output = [
- 'name,make,model'
- ]
- for switch in Device.objects.filter(site=site):
- attrs = [
- switch.name,
- switch.device_type.manufacturer.name,
- switch.device_type.model
- ]
- output.append(','.join(attrs))
-
- return '\n'.join(output)
diff --git a/netbox/scripts/myscripts.py b/netbox/scripts/myscripts.py
deleted file mode 100644
index f3542c368..000000000
--- a/netbox/scripts/myscripts.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from dcim.models import Site
-from extras.scripts import Script, BooleanVar, IntegerVar, ObjectVar, StringVar
-
-
-class NoInputScript(Script):
- description = "This script does not require any input"
-
- def run(self, data):
-
- self.log_debug("This a debug message.")
- self.log_info("This an info message.")
- self.log_success("This a success message.")
- self.log_warning("This a warning message.")
- self.log_failure("This a failure message.")
-
-
-class DemoScript(Script):
- name = "Script Demo"
- description = "A quick demonstration of the available field types"
-
- my_string1 = StringVar(
- description="Input a string between 3 and 10 characters",
- min_length=3,
- max_length=10
- )
- my_string2 = StringVar(
- description="This field enforces a regex: three letters followed by three numbers",
- regex=r'[a-z]{3}\d{3}'
- )
- my_number = IntegerVar(
- description="Pick a number between 1 and 255 (inclusive)",
- min_value=1,
- max_value=255
- )
- my_boolean = BooleanVar(
- description="Use the checkbox to toggle true/false"
- )
- my_object = ObjectVar(
- description="Select a NetBox site",
- queryset=Site.objects.all()
- )
-
- def run(self, data):
-
- self.log_info("Your string was {}".format(data['my_string1']))
- self.log_info("Your second string was {}".format(data['my_string2']))
- self.log_info("Your number was {}".format(data['my_number']))
- if data['my_boolean']:
- self.log_info("You ticked the checkbox")
- else:
- self.log_info("You did not tick the checkbox")
- self.log_info("You chose the sites {}".format(data['my_object']))
-
- return "Here's some output"
diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html
index 60a70c36c..2a347e6e6 100644
--- a/netbox/templates/dcim/rack.html
+++ b/netbox/templates/dcim/rack.html
@@ -135,6 +135,10 @@
{{ rack.devices.count }}
+