diff --git a/python/nav/web/portadmin/utils.py b/python/nav/web/portadmin/utils.py index 7b3d425cda..221b06e170 100644 --- a/python/nav/web/portadmin/utils.py +++ b/python/nav/web/portadmin/utils.py @@ -243,3 +243,11 @@ def is_cisco(netbox): :type netbox: manage.Netbox """ return netbox.type.get_enterprise_id() == VENDOR_ID_CISCOSYSTEMS + + +def add_poe_info(interfaces, handler): + """Add information about PoE state for interfaces""" + states = handler.get_poe_states(interfaces) + for interface in interfaces: + interface.poe_state = states.get(interface.ifname) + interface.supports_poe = True if interface.poe_state else False diff --git a/python/nav/web/portadmin/views.py b/python/nav/web/portadmin/views.py index 7069115f04..23ddb3383a 100644 --- a/python/nav/web/portadmin/views.py +++ b/python/nav/web/portadmin/views.py @@ -46,6 +46,7 @@ mark_detained_interfaces, is_cisco, add_dot1x_info, + add_poe_info, ) from nav.portadmin.config import CONFIG from nav.portadmin.snmp.base import SNMPHandler @@ -55,6 +56,9 @@ NoResponseError, ProtocolError, ManagementError, + POEIndexNotFoundError, + XMLParseError, + POEStateNotSupportedError, ) from .forms import SearchForm from ...portadmin.handlers import DeviceNotConfigurableError @@ -192,6 +196,8 @@ def populate_infodict(request, netbox, interfaces): voice_vlan = None readonly = False handler = None + supports_poe = False + poe_options = [] try: handler = get_and_populate_livedata(netbox, interfaces) @@ -207,6 +213,17 @@ def populate_infodict(request, netbox, interfaces): mark_detained_interfaces(interfaces) if CONFIG.is_dot1x_enabled(): add_dot1x_info(interfaces, handler) + try: + poe_options = handler.get_poe_state_options() + add_poe_info(interfaces, handler) + # Tag poe as being supported if at least one interface supports poe + for interface in interfaces: + if interface.supports_poe: + supports_poe = True + break + except NotImplementedError: + # Only Cisco and Juniper has PoE support currently + pass except NoResponseError: readonly = True messages.error( @@ -230,6 +247,14 @@ def populate_infodict(request, netbox, interfaces): readonly = True messages.error(request, str(error)) + except ( + POEIndexNotFoundError, + XMLParseError, + POEStateNotSupportedError, + ) as error: + readonly = True + messages.error(request, str(error)) + if handler and not handler.is_configurable(): add_readonly_reason(request, handler) readonly = True @@ -260,6 +285,8 @@ def populate_infodict(request, netbox, interfaces): 'aliastemplate': aliastemplate, 'trunk_edit': CONFIG.get_trunk_edit(), 'auditlog_api_parameters': json.dumps(auditlog_api_parameters), + 'supports_poe': supports_poe, + 'poe_options': poe_options, } ) return info_dict @@ -363,11 +390,23 @@ def set_interface_values(account, interface, request): set_ifalias(account, handler, interface, request) set_vlan(account, handler, interface, request) set_admin_status(handler, interface, request) + set_poe_state(handler, interface, request) save_to_database([interface]) else: messages.info(request, 'Could not connect to netbox') +def set_poe_state(handler, interface, request): + if 'poe_state' in request.POST: + poe_state_name = request.POST.get('poe_state') + for option in handler.get_poe_state_options(): + if option.name == poe_state_name: + handler.set_poe_state(interface, option) + return + # If there was no match between posted value and known states + raise ValueError(f"Invalid PoE state name: {poe_state_name}") + + def build_ajax_messages(request): """Create a structure suitable for converting to json from messages""" ajax_messages = [] diff --git a/python/nav/web/static/js/src/portadmin.js b/python/nav/web/static/js/src/portadmin.js index 1e146cf5a9..4b1224d1d7 100644 --- a/python/nav/web/static/js/src/portadmin.js +++ b/python/nav/web/static/js/src/portadmin.js @@ -176,13 +176,16 @@ require(['libs/spin.min', 'libs/jquery-ui.min'], function (Spinner) { $wrapper.on('change', '.ifadminstatus', function (event) { actOnChange($(event.target).parents(parentSelector)); }); + $wrapper.on('change', '.poelist', function (event) { + actOnChange($(event.target).parents(parentSelector)); + }); } /* * Mark card changed or not based on values in card */ function actOnChange(row) { - if (textFieldChanged(row) || dropDownChanged(row) || voiceVlanChanged(row) || adminStatusChanged(row)) { + if (textFieldChanged(row) || dropDownChanged(row) || voiceVlanChanged(row) || adminStatusChanged(row) || poeDropDownChanged(row)) { markAsChanged(row); } else { markAsUnchanged(row); @@ -219,6 +222,13 @@ require(['libs/spin.min', 'libs/jquery-ui.min'], function (Spinner) { return origOption !== selectedOption; } + function poeDropDownChanged(row) { + var dropdown = $(row).find(".poelist"); + var origOption = $('[data-orig]', dropdown)[0]; + var selectedOption = $('option:selected', dropdown)[0]; + return origOption !== selectedOption; + } + function voiceVlanChanged(row) { /* * XOR checkbox checked and original value to see if changed @@ -303,6 +313,9 @@ require(['libs/spin.min', 'libs/jquery-ui.min'], function (Spinner) { data.ifadminstatus = 2; } } + if (poeDropDownChanged($row)) { + data.poe_state = $row.find(".poelist").val(); + } if ($row.find(".voicevlan").prop('checked')) { data.voice_activated = true; } diff --git a/python/nav/web/templates/portadmin/portlist.html b/python/nav/web/templates/portadmin/portlist.html index 7a343cf8b5..e6f40e6a98 100644 --- a/python/nav/web/templates/portadmin/portlist.html +++ b/python/nav/web/templates/portadmin/portlist.html @@ -25,7 +25,7 @@
-