Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend for configuring PoE in Portadmin #2759

Merged
merged 6 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions python/nav/web/portadmin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 39 additions & 0 deletions python/nav/web/portadmin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -55,6 +56,9 @@
NoResponseError,
ProtocolError,
ManagementError,
POEIndexNotFoundError,
XMLParseError,
POEStateNotSupportedError,
)
from .forms import SearchForm
from ...portadmin.handlers import DeviceNotConfigurableError
Expand Down Expand Up @@ -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)
Expand All @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = []
Expand Down
15 changes: 14 additions & 1 deletion python/nav/web/static/js/src/portadmin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
27 changes: 25 additions & 2 deletions python/nav/web/templates/portadmin/portlist.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<div class="hide-for-small medium-1 column">
Linked
</div>
<div class="hide-for-small medium-4 column">
<div class="hide-for-small {% if supports_poe %}medium-3{% else %}medium-4{% endif %} column">
Port description
</div>
<div class="hide-for-small medium-2 column">
Expand All @@ -36,6 +36,11 @@
Tel
</div>
{% endif %}
{% if supports_poe %}
<div class="hide-for-small medium-1 column">
PoE State
</div>
{% endif %}
<div class="column {% if voice_vlan %}medium-1{% else %}medium-2{% endif %} text-right">
{% if not readonly %}
<input type="button" class="saveall_button button tiny" value="Save all"/>
Expand Down Expand Up @@ -76,7 +81,7 @@
</div>

{# Port Description - input field #}
<div class="medium-4 column">
<div class="{% if supports_poe %}medium-3{% else %}medium-4{% endif %} column">
{% if interface.iseditable and not readonly %}
<input class="ifalias" type="text"
value="{{ interface.ifalias|default_if_none:'' }}"
Expand Down Expand Up @@ -162,6 +167,24 @@
</div>
{% endif %}

{# POE STATE #}
{% if supports_poe %}
{% if interface.supports_poe %}
<div class="medium-1 small-4 column">
<form class="custom">
<select class="poelist" name="{{ interface.ifname }}">
{% for poe_option in poe_options %}
<option value="{{ poe_option.name }}" label="{{ poe_option.name }}"
{% if interface.poe_state.name == poe_option.name %}selected="selected"
data-orig="{{ poe_option.name }}"{% endif %}>
{{ vlan }}
{% endfor %}
</select>
</form>
</div>
{% endif %}
{% endif %}

{# Button for saving #}
<div class="{% if voice_vlan %}medium-1 small-4{% else %}medium-2 small-8{% endif %} column text-right">
{% if interface.iseditable and not readonly %}
Expand Down