Skip to content

Commit

Permalink
Merge branch 'main' into 32-import-device-export-file-from-matrix
Browse files Browse the repository at this point in the history
  • Loading branch information
weidenba committed Jan 11, 2025
2 parents 7546cb3 + c1ddbc7 commit a7bb51b
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 76 deletions.
5 changes: 4 additions & 1 deletion src/nac/subviews/account.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.shortcuts import render, redirect
from django.views.generic import TemplateView


class AccountSettings(TemplateView):
class AccountSettings(LoginRequiredMixin, TemplateView):
template_name = "account_settings.html"


@login_required
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
Expand Down
4 changes: 3 additions & 1 deletion src/nac/subviews/armis.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
from django.views.generic import View
from django.core.cache import cache
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin


from helper.armis import get_armis_sites, get_devices, get_tenant_url, get_boundaries, map_ids_to_names


class ArmisView(View):
class ArmisView(LoginRequiredMixin, View):
template_name = "armis_import.html"

def _get_context(self): # sets the site-context for armis_import.html, uses cache to be less time consuming
Expand Down
7 changes: 4 additions & 3 deletions src/nac/subviews/autocomplete.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from dal import autocomplete
from ..models import DeviceRoleProd, AuthorizationGroup, DeviceRoleInst
from django.contrib.auth.mixins import LoginRequiredMixin


class DeviceRoleProdAutocomplete(autocomplete.Select2QuerySetView):
class DeviceRoleProdAutocomplete(LoginRequiredMixin, autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return DeviceRoleProd.objects.none()
Expand All @@ -23,7 +24,7 @@ def get_queryset(self):
return qs


class DeviceRoleInstAutocomplete(autocomplete.Select2QuerySetView):
class DeviceRoleInstAutocomplete(LoginRequiredMixin, autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return DeviceRoleInst.objects.none()
Expand All @@ -40,7 +41,7 @@ def get_queryset(self):
return qs


class AuthorizationGroupAutocomplete(autocomplete.Select2QuerySetView):
class AuthorizationGroupAutocomplete(LoginRequiredMixin, autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return AuthorizationGroup.objects.none()
Expand Down
25 changes: 19 additions & 6 deletions src/nac/subviews/device_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
from django.db.models import Q
from django.urls import reverse_lazy
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
import json
from django.http import JsonResponse
from django.template.loader import render_to_string

from ..models import Device, AuthorizationGroup, DeviceRoleProd
from ..forms import DeviceForm, DeviceSearchForm
from ..validation import normalize_mac


class DeviceListView(ListView):
class DeviceListView(LoginRequiredMixin, ListView):
model = Device
template_name = "devices.html"
context_object_name = "device_list"
Expand Down Expand Up @@ -38,33 +41,43 @@ def get_queryset(self):
device_list = device_list.filter(appl_NAC_DeviceRoleProd__in=selected_device_roles_prod)
return device_list.order_by("name")

def get(self, request, *args, **kwargs):
# Check if the request is an AJAX request
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
# Handle AJAX request by rendering only the relevant part of the template
html = render_to_string('devices_results.html', {"device_list": self.get_queryset()})
return JsonResponse({'html': html})

# Otherwise, handle a normal HTTP request
return super().get(request, *args, **kwargs)

# we need this for the drop-down menus with filtering options
def get_context_data(self, *, object_list=None, **kwargs):
def get_context_data(self, **kwargs):
context = super(DeviceListView, self).get_context_data(**kwargs)
context["auth_group_list"] = AuthorizationGroup.objects.filter(id__in=self.request.user.authorization_group.all())
context["device_role_prod_list"] = DeviceRoleProd.objects.all()
context["search_form"] = DeviceSearchForm(user=self.request.user)
return context


class DeviceDetailView(DetailView):
class DeviceDetailView(LoginRequiredMixin, DetailView):
model = Device
template_name = "device_detail.html"


class DeviceUpdateView(UpdateView):
class DeviceUpdateView(LoginRequiredMixin, UpdateView):
model = Device
form_class = DeviceForm
template_name = "device_edit.html"


class DeviceDeleteView(DeleteView):
class DeviceDeleteView(LoginRequiredMixin, DeleteView):
model = Device
template_name = "device_delete.html"
success_url = reverse_lazy("devices")


class DeviceCreateView(CreateView):
class DeviceCreateView(LoginRequiredMixin, CreateView):
model = Device
form_class = DeviceForm
template_name = "device_new.html"
Expand Down
30 changes: 30 additions & 0 deletions src/static/device_filtering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const search_form = $("#search_form")
const results_div = $('#results')
const endpoint = '/devices/'
const delay_by_in_ms = 700
let scheduled_function = false

let ajax_call = function (endpoint, request_parameters) {
$.getJSON(endpoint, request_parameters)
.done(response => {
// replace the HTML contents
results_div.html(response['html'])
})
}

search_form.on('input', function () {

const request_parameters = {
search_string: $('#id_search_string').val(),
device_role_prod: $('#id_device_role_prod').val(),
authorization_group: $('#id_authorization_group').val()
}

// if scheduled_function is NOT false, cancel the execution of the function
if (scheduled_function) {
clearTimeout(scheduled_function)
}

// setTimeout returns the ID of the function to be executed
scheduled_function = setTimeout(ajax_call, delay_by_in_ms, endpoint, request_parameters);
})
5 changes: 4 additions & 1 deletion src/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
<link rel="icon" href="{% static 'icons/favicon.svg' %}" sizes="any" type="image/svg+xml">

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>


<!-- JQuery -->
<script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" crossorigin="anonymous"></script>


{% block head %}{% endblock %}

Expand Down
72 changes: 8 additions & 64 deletions src/templates/devices.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{% block content %}
<div class="container">
<h1>Devices</h1>
<form action="{% url 'devices' %}" method="get">
<form id="search_form" action="{% url 'devices' %}" method="get">
<div class="row">
{% for field in search_form %}
<div class="col-md-4 ">
Expand All @@ -15,68 +15,12 @@ <h1>Devices</h1>
</div>
<button type="submit" class="btn btn-secondary btn-sm">Filter</button>
</form>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Status</th>
<th scope="col">FQDN</th>
<th scope="col">Device Role Prod</th>
<th scope="col">MAC</th>
<th scope="col" width="60"></th>
</tr>
</thead>

{% for device in device_list %}
<tr>
<td>
<a href="{% url 'device_detail' device.pk %}">{{ device.name }}</a>
</td>
<td>
{% if device.appl_NAC_Active %}
<span class="icon-desc" desc="Active Device"><img src="{% static 'icons/check-lg.svg' %}" alt="appl_NAC_Active_True" height="25" width="25" class="icon-zoom" /></span>
{% else %}
<span class="icon-desc" desc="Inactive Device"><img src="{% static 'icons/x-lg.svg' %}" alt="appl_NAC_Active_False" height="25" width="25" class="icon-zoom" /></span>
{% endif %}
{% if device.appl_NAC_Install %}
<span class="icon-desc" desc="Install"><img src="{% static 'icons/wrench.svg' %}" alt="appl_NAC_Install" height="25" width="25" class="icon-zoom"/></span>
{% endif %}
</td>
<td>
{{ device.appl_NAC_FQDN }}
</td>
<td>
{{ device.appl_NAC_DeviceRoleProd }}
</td>
<td>
{% for mac in device.get_appl_NAC_macAddressAIR %}
<b>
{% if mac != None %}
<span class="icon-desc" desc="macAddressAIR"><img src="{% static 'icons/wifi.svg' %}" alt="appl_NAC_macAddressAIR" height="14" width="14" class="icon-zoom"/></span>
<b> {{mac}}</b> <br />
{% endif %}
</b>
{% endfor %}
{% for mac in device.get_appl_NAC_macAddressCAB %}
<b>
{% if mac != None %}
<span class="icon-desc" desc="macAddressCAB"><img src="{% static 'icons/ethernet.svg' %}" alt="appl_NAC_macAddressCAB" height="14" width="14" class="icon-zoom"/></span>
<b> {{mac}}</b> <br />
{% endif %}
</b>
{% endfor %}
</td>
<td>
<a href="{% url 'device_edit' device.pk %}">
<span class="icon-desc" desc="edit"><img src="{% static 'icons/pencil-square.svg' %}" alt="edit" height="16" width="16" class="icon-zoom"/></span>

</a>
<a href="{% url 'device_delete' device.pk %}">
<span class="icon-desc" desc="delete"><img src="{% static 'icons/trash.svg' %}" alt="delete" height="16" width="16" class="icon-zoom"/></span>
</a>
</td>
</tr>
{% endfor %}
</table>
<div id="results">
{% include "devices_results.html" %}
</div>
</div>
{% endblock content %}

<script type="text/javascript" src="{% static 'device_filtering.js' %}"></script>

{% endblock content %}
66 changes: 66 additions & 0 deletions src/templates/devices_results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{% load static %}

<table class="table table-striped">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Status</th>
<th scope="col">FQDN</th>
<th scope="col">Device Role Prod</th>
<th scope="col">MAC</th>
<th scope="col" width="60"></th>
</tr>
</thead>

{% for device in device_list %}
<tr>
<td>
<a href="{% url 'device_detail' device.pk %}">{{ device.name }}</a>
</td>
<td>
{% if device.appl_NAC_Active %}
<span class="icon-desc" desc="Active Device"><img src="{% static 'icons/check-lg.svg' %}" alt="appl_NAC_Active_True" height="25" width="25" class="icon-zoom" /></span>
{% else %}
<span class="icon-desc" desc="Inactive Device"><img src="{% static 'icons/x-lg.svg' %}" alt="appl_NAC_Active_False" height="25" width="25" class="icon-zoom" /></span>
{% endif %}
{% if device.appl_NAC_Install %}
<span class="icon-desc" desc="Install"><img src="{% static 'icons/wrench.svg' %}" alt="appl_NAC_Install" height="25" width="25" class="icon-zoom"/></span>
{% endif %}
</td>
<td>
{{ device.appl_NAC_FQDN }}
</td>
<td>
{{ device.appl_NAC_DeviceRoleProd }}
</td>
<td>
{% for mac in device.get_appl_NAC_macAddressAIR %}
<b>
{% if mac != None %}
<span class="icon-desc" desc="macAddressAIR"><img src="{% static 'icons/wifi.svg' %}" alt="appl_NAC_macAddressAIR" height="14" width="14" class="icon-zoom"/></span>
<b> {{mac}}</b> <br />
{% endif %}
</b>
{% endfor %}
{% for mac in device.get_appl_NAC_macAddressCAB %}
<b>
{% if mac != None %}
<span class="icon-desc" desc="macAddressCAB"><img src="{% static 'icons/ethernet.svg' %}" alt="appl_NAC_macAddressCAB" height="14" width="14" class="icon-zoom"/></span>
<b> {{mac}}</b> <br />
{% endif %}
</b>
{% endfor %}
</td>
<td>
<a href="{% url 'device_edit' device.pk %}">
<span class="icon-desc" desc="edit"><img src="{% static 'icons/pencil-square.svg' %}" alt="edit" height="16" width="16" class="icon-zoom"/></span>

</a>
<a href="{% url 'device_delete' device.pk %}">
<span class="icon-desc" desc="delete"><img src="{% static 'icons/trash.svg' %}" alt="delete" height="16" width="16" class="icon-zoom"/></span>
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
22 changes: 22 additions & 0 deletions src/tests/test_views/test_device_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ def test_device_search(query, result):
assertQuerySetEqual(desired_qs, result_qs, ordered=False)


@pytest.mark.django_db
def test_result_rendering(client):
test_user = CustomUser.objects.create(name="test")
test_user.set_password("test")
test_user.authorization_group.set([AuthorizationGroup.objects.get(pk=1)])
test_user.save()

client.force_login(test_user)

url = reverse_lazy('devices')
response = client.get(url)
assert response.status_code == 200

ajax_response = client.get(
url, # The URL where the AJAX request is sent
{"search_string": "", "authorization_group": "", "device_role_prod": ""}, # Parameters to be sent in the AJAX request
HTTP_X_REQUESTED_WITH='XMLHttpRequest' # Indicate it's an AJAX request
)

assert ajax_response.status_code == 200


@pytest.mark.django_db
@pytest.mark.parametrize("auth_group, device_role_prod, result",
[("", "", [1, 2, 3, 4, 5]),
Expand Down

0 comments on commit a7bb51b

Please sign in to comment.