Repository: netbox-community/netbox
Pull request: Fixes #3090: Interface filtering
### Fixes: #3090

Add a filter for device interfaces that updates the selection as changed. It is RegEx based just because it is convenient for actions that span multiple chassises, like creating an MC-LAG.

![interface-filter](https://user-images.githubusercontent.com/34197532/71746439-ced69c80-2e64-11ea-925d-c4f19fce1c16.gif)


Problem statement:
Allow filtering interfaces by type on device view
### Environment
* Python version:  3.5.6
* NetBox version:  2.5.10

### Proposed Functionality
Add a dropdown menu at the top of the device interfaces tables (next to "show IPs") to allow filtering interfaces by form factor/type. ~We don't have any sufficient UI framework in place yet to do this dynamically, so for now we're looking at a full page refresh: A link to something like `/dcim/devices/123/?interface_type=800`.~ A search box with some jQuery to display only interfaces with a matching name should suffice.

### Use Case
Some switches, especially large chassis switches, can have hundreds of interfaces. Sometimes a user may want to edit all interfaces of a certain type in bulk. Currently, this requires selecting each interface individually.

### Database Changes
None

### External Dependencies
None

Hints from issue discussion:
Maybe also a filter for connected/not connected or enabled/disabled? I don't know that I see a benefit to having all fields filterable here.  However, being able to sort by any column on the fly would be nice.
I would also suggest adding a count of interfaces to the table header, so users know how many are filtered out.  Mock up images attached.

![image](https://user-images.githubusercontent.com/26419758/56431526-d6c2c200-627e-11e9-98af-2c84122b94cd.png)
![image](https://user-images.githubusercontent.com/26419758/56431535-e17d5700-627e-11e9-86bd-d49a5ea44dbe.png)

Commit message: Merge branch 'develop' into 3090-interface-filtering

Patch to review:
diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md
index b2ddb7c7c69..6792e30a0c4 100644
--- a/docs/release-notes/version-2.6.md
+++ b/docs/release-notes/version-2.6.md
@@ -3,6 +3,7 @@
 ## Enhancements
 
 * [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link
+* [#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
 
 ---
diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html
index 57e2b03b851..3b3beb3e0e4 100644
--- a/netbox/templates/dcim/device.html
+++ b/netbox/templates/dcim/device.html
@@ -556,6 +556,9 @@ <h1>{{ device }}</h1>
                                 <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
                             </button>
                         </div>
+                        <div class="col-md-2 pull-right noprint">
+                            <input class="form-control interface-filter" type="text" placeholder="Filter (RegExp)" style="height: 23px" />
+                        </div>
                     </div>
                     <table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
                         <thead>
@@ -912,6 +915,22 @@ <h1>{{ device }}</h1>
     $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
     return false;
 });
+$('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();
+        }
+    }
+});
 </script>
 <script src="{% static 'js/graphs.js' %}?v{{ settings.VERSION }}"></script>
 <script src="{% static 'js/secrets.js' %}?v{{ settings.VERSION }}"></script>
diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html
index 2498039ff1d..298daf94f2f 100644
--- a/netbox/templates/virtualization/virtualmachine.html
+++ b/netbox/templates/virtualization/virtualmachine.html
@@ -253,6 +253,9 @@ <h1>{% block title %}{{ virtualmachine }}{% endblock %}</h1>
                         <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
                     </button>
                 </div>
+                <div class="col-md-2 pull-right noprint">
+                    <input class="form-control interface-filter" type="text" placeholder="Filter (RegExp)" style="height: 23px" />
+                </div>
             </div>
             <table id="interfaces_table" class="table table-hover table-headings panel-body component-list">
                 <thead>
@@ -325,5 +328,21 @@ <h1>{% block title %}{{ virtualmachine }}{% endblock %}</h1>
     $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
     return false;
 });
+$('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();
+        }
+    }
+});
 </script>
 {% endblock %}


Code review comments:
diff_hunk: @@ -556,6 +556,9 @@ <h1>{{ device }}</h1>
                                 <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
                             </button>
                         </div>
+                        <div class="col-md-2 pull-right noprint">
+                            <input class="form-control interface-filter" type="text" placeholder="Filter (RegExp)" style="height: 23px" />
path: netbox/templates/dcim/device.html
text: @user1:
IMO we should leave the placeholder text as "Filter" since some people won't know what RegExp refers to. It might be nice to add a little question mark icon (`fa-question-circle`) next to the filter field that would pop up a little explanation, but not really necessary.

diff_hunk: @@ -325,5 +328,21 @@ <h1>{% block title %}{{ virtualmachine }}{% endblock %}</h1>
     $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
     return false;
 });
+$('input.interface-filter').on('input', function() {
path: netbox/templates/virtualization/virtualmachine.html
text: @user1:
Can you move this code somewhere so that it can be referenced by either template without being repeated? Maybe `templates/dcim/inc/interface_toggles.js`? (The "toggle IP" bit above it can be moved too.) I just know that if we keep two separate copies of the code we'll inevitably forget to update one at some point.

@author:
For the sake of performance (caching), I'm gonna put it as a static `.js` file.