464 lines
19 KiB
JavaScript
464 lines
19 KiB
JavaScript
/*
|
|
* Copyright 2022, Digi International Inc.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
// Constants.
|
|
const ELEMENT_ETHERNET = "ethernet";
|
|
const ELEMENT_WIFI = "wifi";
|
|
|
|
const FIELD_DEFAULT_GATEWAY = "_default_gateway";
|
|
const FIELD_DNS1_ADDRESS = "_dns1_addr";
|
|
const FIELD_DNS2_ADDRESS = "_dns2_addr";
|
|
const FIELD_ENABLE = "_enable";
|
|
const FIELD_ENCRYPTION_TYPE = "_enc_type";
|
|
const FIELD_ERROR = "_error";
|
|
const FIELD_MAC_ADDRESS = "_mac";
|
|
const FIELD_IP_ADDRESS = "_ip_addr";
|
|
const FIELD_IP_MODE = "_ip_mode";
|
|
const FIELD_PARAM = "_param";
|
|
const FIELD_PASSWORD = "_password";
|
|
const FIELD_SAVE_BUTTON = "_save_button";
|
|
const FIELD_SSID = "_ssid";
|
|
const FIELD_SUBNET_MASK = "_subnet_mask";
|
|
|
|
const ID_DNS1 = "dns1";
|
|
const ID_DNS2 = "dns2";
|
|
const ID_ENABLE = "enable";
|
|
const ID_ETH0_TITLE = "eth0_title";
|
|
const ID_GATEWAY = "gateway";
|
|
const ID_IP = "ip";
|
|
const ID_IP_MODE = "type";
|
|
const ID_INTERFACE = "interface";
|
|
const ID_MAC = "mac";
|
|
const ID_NETMASK = "netmask";
|
|
const ID_PANEL_CONTAINER = "_panel_container";
|
|
const ID_PASSWORD = "psk";
|
|
const ID_SECURITY_MODE = "sec_mode";
|
|
const ID_SSID = "ssid";
|
|
const ID_TOGGLE_BUTTON = "_toggle_button";
|
|
|
|
const IP_MODE_DHCP = "dhcp";
|
|
const IP_MODE_STATIC = "static";
|
|
|
|
const ERROR_IP_VALUE_FORMAT = "Value must follow this format: XXX.XXX.XXX.XXX";
|
|
const ERROR_PASSWORD_INVALID = "Password must be between 8 and 63 characters long";
|
|
const ERROR_SSID_INVALID = "SSID value is not valid";
|
|
const ERROR_UNKNOWN = "Unknown error saving configuration.";
|
|
|
|
const MESSAGE_LOADING_CONFIGURATION = "Loading configuration...";
|
|
const MESSAGE_SAVING_CONFIGURATION = "Saving configuration...";
|
|
const MESSAGE_CONFIGURATION_SAVED = "Configuration saved successfully!";
|
|
|
|
const PREFIX_ETHERNET = "eth";
|
|
const PREFIX_WIFI = "wlan";
|
|
|
|
const REGEX_IP = '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$';
|
|
const REGEX_SSID = '^[^!#;+\\]/"\t][^+\\]/"\t]{0,31}$';
|
|
const REGEX_PASSWORD = '^[\u0020-\u007e]{8,63}$';
|
|
|
|
const ALL_ELEMENTS = "all";
|
|
|
|
// Variables.
|
|
var readingNetworkInfo = false;
|
|
var passwordChanged = false;
|
|
|
|
// Initializes the network page.
|
|
function initializeNetworkPage() {
|
|
// Sanity checks.
|
|
if (!isNetworkShowing())
|
|
return;
|
|
// Read network configuration.
|
|
readElements = [ELEMENT_ETHERNET, ELEMENT_WIFI];
|
|
readConfiguration(readElements, ALL_ELEMENTS);
|
|
}
|
|
|
|
// Gets the device configuration the given elements.
|
|
function readConfiguration(readElements, parseElement) {
|
|
// Execute only in the network page.
|
|
if (!isNetworkShowing() || readingNetworkInfo)
|
|
return;
|
|
// Flag reading variable.
|
|
readingNetworkInfo = true;
|
|
// Hide the info popup.
|
|
showInfoPopup(false);
|
|
// Show the loading popup.
|
|
showLoadingPopup(true, MESSAGE_LOADING_CONFIGURATION);
|
|
// Send request to retrieve configuration.
|
|
$.post(
|
|
"http://" + getServerAddress() + "/ajax/get_config",
|
|
JSON.stringify({
|
|
"elements": readElements
|
|
}),
|
|
function(data) {
|
|
// Process only in the network page.
|
|
if (!isNetworkShowing())
|
|
return;
|
|
// Process answer.
|
|
processReadConfigurationResponse(data, parseElement);
|
|
}
|
|
).fail(function(response) {
|
|
// Flag reading variable.
|
|
readingNetworkInfo = false;
|
|
// Clear the element being read.
|
|
elementReading = "";
|
|
// Process only in the network page.
|
|
if (!isNetworkShowing())
|
|
return;
|
|
// Hide the loading panel.
|
|
showLoadingPopup(false);
|
|
// Process error.
|
|
processAjaxErrorResponse(response);
|
|
// Update controls.
|
|
updateAllControls();
|
|
});
|
|
}
|
|
|
|
// Processes the response of the read configuration request.
|
|
function processReadConfigurationResponse(response, parseElement) {
|
|
// Check if there was any error in the request.
|
|
if (!checkErrorResponse(response, false)) {
|
|
// Fill network info.
|
|
fillNetworkInfo(JSON.parse(response[ID_DATA]), parseElement);
|
|
} else {
|
|
// Flag reading variable.
|
|
readingNetworkInfo = false;
|
|
// Hide the loading panel.
|
|
showLoadingPopup(false);
|
|
}
|
|
}
|
|
|
|
// Fills network information.
|
|
function fillNetworkInfo(response, parseElement) {
|
|
var numEthernetIfaces = 0;
|
|
var numWifiIfaces = 0;
|
|
for (const [element, elementData] of Object.entries(response)) {
|
|
if (element != ELEMENT_ETHERNET && element != ELEMENT_WIFI)
|
|
continue;
|
|
for (const [iface, ifaceData] of Object.entries(elementData)) {
|
|
if (!iface.startsWith(PREFIX_WIFI) && !iface.startsWith(PREFIX_ETHERNET))
|
|
continue;
|
|
if (parseElement != ALL_ELEMENTS && iface != parseElement)
|
|
continue;
|
|
var enabled = ifaceData[ID_ENABLE];
|
|
var ipMode = ifaceData[ID_IP_MODE];
|
|
if (iface.startsWith(PREFIX_ETHERNET))
|
|
numEthernetIfaces += 1;
|
|
else if (iface.startsWith(PREFIX_WIFI))
|
|
numWifiIfaces += 1;
|
|
// Fill MAC.
|
|
document.getElementById(iface + FIELD_MAC_ADDRESS).innerText = ifaceData[ID_MAC];
|
|
// Set enable state.
|
|
document.getElementById(iface + FIELD_ENABLE).checked = enabled;
|
|
// Set IP mode.
|
|
if (ipMode == 0)
|
|
document.getElementById(iface + FIELD_IP_MODE).value = IP_MODE_STATIC;
|
|
else
|
|
document.getElementById(iface + FIELD_IP_MODE).value = IP_MODE_DHCP;
|
|
// Fill the rest of the fields.
|
|
document.getElementById(iface + FIELD_IP_ADDRESS).value = ifaceData[ID_IP];
|
|
document.getElementById(iface + FIELD_SUBNET_MASK).value = ifaceData[ID_NETMASK];
|
|
document.getElementById(iface + FIELD_DEFAULT_GATEWAY).value = ifaceData[ID_GATEWAY];
|
|
document.getElementById(iface + FIELD_DNS1_ADDRESS).value = ifaceData[ID_DNS1];
|
|
document.getElementById(iface + FIELD_DNS2_ADDRESS).value = ifaceData[ID_DNS2];
|
|
// Specific Wifi fields.
|
|
if (iface.startsWith(PREFIX_WIFI)) {
|
|
document.getElementById(iface + FIELD_SSID).value = ifaceData[ID_SSID];
|
|
document.getElementById(iface + FIELD_ENCRYPTION_TYPE).selectedIndex = ifaceData[ID_SECURITY_MODE];
|
|
if (ifaceData[ID_SECURITY_MODE] != 0)
|
|
document.getElementById(iface + FIELD_PASSWORD).value = "dummy_password";
|
|
passwordChanged = false;
|
|
}
|
|
// Update controls.
|
|
updateInterfaceControls(iface);
|
|
}
|
|
}
|
|
// Update interfaces visibility.
|
|
if (parseElement == ALL_ELEMENTS) {
|
|
if (numEthernetIfaces == 1) {
|
|
document.getElementById(IFACE_ETH1).style.display = "none";
|
|
document.getElementById(IFACE_ETH2).style.display = "none";
|
|
document.getElementById(ID_ETH0_TITLE).innerHTML = "Ethernet";
|
|
} else if (numEthernetIfaces == 2) {
|
|
document.getElementById(IFACE_ETH2).style.display = "none";
|
|
}
|
|
if (numWifiIfaces == 0)
|
|
document.getElementById(IFACE_WIFI).style.display = "none";
|
|
}
|
|
// Flag reading variable.
|
|
readingNetworkInfo = false;
|
|
// Hide the loading panel.
|
|
showLoadingPopup(false);
|
|
}
|
|
|
|
// Updates all controls of the page.
|
|
function updateAllControls() {
|
|
updateInterfaceControls(IFACE_ETH0);
|
|
updateInterfaceControls(IFACE_ETH1);
|
|
updateInterfaceControls(IFACE_ETH2);
|
|
updateInterfaceControls(IFACE_WIFI);
|
|
}
|
|
|
|
// Updates the given interface controls based on its configuration.
|
|
function updateInterfaceControls(iface) {
|
|
// Initialize variables.
|
|
var ipGroup = document.getElementById(iface + FIELD_IP_ADDRESS + FIELD_PARAM);
|
|
var ipField = document.getElementById(iface + FIELD_IP_ADDRESS);
|
|
var subnetGroup = document.getElementById(iface + FIELD_SUBNET_MASK + FIELD_PARAM);
|
|
var subnetField = document.getElementById(iface + FIELD_SUBNET_MASK);
|
|
var gatewayGroup = document.getElementById(iface + FIELD_DEFAULT_GATEWAY + FIELD_PARAM);
|
|
var gatewayField = document.getElementById(iface + FIELD_DEFAULT_GATEWAY);
|
|
var dns1Group = document.getElementById(iface + FIELD_DNS1_ADDRESS + FIELD_PARAM);
|
|
var dns1Field = document.getElementById(iface + FIELD_DNS1_ADDRESS);
|
|
var dns2Group = document.getElementById(iface + FIELD_DNS2_ADDRESS + FIELD_PARAM);
|
|
var dns2Field = document.getElementById(iface + FIELD_DNS2_ADDRESS);
|
|
var ipModeGroup = document.getElementById(iface + FIELD_IP_MODE + FIELD_PARAM);
|
|
var ipModeField = document.getElementById(iface + FIELD_IP_MODE);
|
|
var enableField = document.getElementById(iface + FIELD_ENABLE);
|
|
var enabled = enableField.checked;
|
|
var ipMode = ipModeField.value;
|
|
var ssidGroup, encryptionTypeGroup, encryptionTypeField, passwordGroup, usePassword;
|
|
if (iface.startsWith(PREFIX_WIFI)) {
|
|
ssidGroup = document.getElementById(iface + FIELD_SSID + FIELD_PARAM);
|
|
encryptionTypeGroup = document.getElementById(iface + FIELD_ENCRYPTION_TYPE + FIELD_PARAM);
|
|
encryptionTypeField = document.getElementById(iface + FIELD_ENCRYPTION_TYPE);
|
|
passwordGroup = document.getElementById(iface + FIELD_PASSWORD + FIELD_PARAM);
|
|
usePassword = encryptionTypeField.value != 0;
|
|
if (usePassword)
|
|
passwordGroup.style.display = "block";
|
|
else
|
|
passwordGroup.style.display = "none";
|
|
}
|
|
// Determine if IP fields can be edited.
|
|
if (ipMode == IP_MODE_DHCP) {
|
|
if (!ipField.classList.contains(CLASS_INPUT_DISABLED))
|
|
ipField.classList.add(CLASS_INPUT_DISABLED);
|
|
if (!subnetField.classList.contains(CLASS_INPUT_DISABLED))
|
|
subnetField.classList.add(CLASS_INPUT_DISABLED);
|
|
if (!gatewayField.classList.contains(CLASS_INPUT_DISABLED))
|
|
gatewayField.classList.add(CLASS_INPUT_DISABLED);
|
|
if (!dns1Field.classList.contains(CLASS_INPUT_DISABLED))
|
|
dns1Field.classList.add(CLASS_INPUT_DISABLED);
|
|
if (!dns2Field.classList.contains(CLASS_INPUT_DISABLED))
|
|
dns2Field.classList.add(CLASS_INPUT_DISABLED);
|
|
} else if (ipMode == IP_MODE_STATIC) {
|
|
if (ipField.classList.contains(CLASS_INPUT_DISABLED))
|
|
ipField.classList.remove(CLASS_INPUT_DISABLED);
|
|
if (subnetField.classList.contains(CLASS_INPUT_DISABLED))
|
|
subnetField.classList.remove(CLASS_INPUT_DISABLED);
|
|
if (gatewayField.classList.contains(CLASS_INPUT_DISABLED))
|
|
gatewayField.classList.remove(CLASS_INPUT_DISABLED);
|
|
if (dns1Field.classList.contains(CLASS_INPUT_DISABLED))
|
|
dns1Field.classList.remove(CLASS_INPUT_DISABLED);
|
|
if (dns2Field.classList.contains(CLASS_INPUT_DISABLED))
|
|
dns2Field.classList.remove(CLASS_INPUT_DISABLED);
|
|
}
|
|
// Validate the network interface.
|
|
validateInterface(iface);
|
|
}
|
|
|
|
// Validates the given network interface.
|
|
function validateInterface(iface) {
|
|
// Initialize vars.
|
|
var valid = true;
|
|
var saveButton = document.getElementById(iface + FIELD_SAVE_BUTTON);
|
|
var enableField = document.getElementById(iface + FIELD_ENABLE);
|
|
var ipModeField = document.getElementById(iface + FIELD_IP_MODE);
|
|
var ipField = document.getElementById(iface + FIELD_IP_ADDRESS);
|
|
var subnetField = document.getElementById(iface + FIELD_SUBNET_MASK);
|
|
var gatewayField = document.getElementById(iface + FIELD_DEFAULT_GATEWAY);
|
|
var dns1Field = document.getElementById(iface + FIELD_DNS1_ADDRESS);
|
|
var dns2Field = document.getElementById(iface + FIELD_DNS2_ADDRESS);
|
|
var ipMode = ipModeField.value;
|
|
var forceValid = false;
|
|
// Validate fields.
|
|
if (ipMode == IP_MODE_DHCP)
|
|
forceValid = true;
|
|
valid &= validateIPField(iface + FIELD_IP_ADDRESS, iface + FIELD_IP_ADDRESS + FIELD_ERROR, forceValid);
|
|
valid &= validateIPField(iface + FIELD_SUBNET_MASK, iface + FIELD_SUBNET_MASK + FIELD_ERROR, forceValid);
|
|
valid &= validateIPField(iface + FIELD_DEFAULT_GATEWAY, iface + FIELD_DEFAULT_GATEWAY + FIELD_ERROR, forceValid);
|
|
valid &= validateIPField(iface + FIELD_DNS1_ADDRESS, iface + FIELD_DNS1_ADDRESS + FIELD_ERROR, forceValid);
|
|
valid &= validateIPField(iface + FIELD_DNS2_ADDRESS, iface + FIELD_DNS2_ADDRESS + FIELD_ERROR, forceValid);
|
|
if (iface.startsWith(PREFIX_WIFI)) {
|
|
valid &= validateField(iface + FIELD_SSID, iface + FIELD_SSID + FIELD_ERROR, REGEX_SSID, ERROR_SSID_INVALID, false);
|
|
if (parseInt(document.getElementById(iface + FIELD_ENCRYPTION_TYPE).value) != 0)
|
|
valid &= validateField(iface + FIELD_PASSWORD, iface + FIELD_PASSWORD + FIELD_ERROR, REGEX_PASSWORD, ERROR_PASSWORD_INVALID, false);
|
|
}
|
|
// Check errors.
|
|
if (!valid) {
|
|
if (!saveButton.classList.contains(CLASS_CONFIG_BUTTON_DISABLED))
|
|
saveButton.classList.add(CLASS_CONFIG_BUTTON_DISABLED);
|
|
} else {
|
|
if (saveButton.classList.contains(CLASS_CONFIG_BUTTON_DISABLED))
|
|
saveButton.classList.remove(CLASS_CONFIG_BUTTON_DISABLED);
|
|
}
|
|
}
|
|
|
|
// Validates the given IP field.
|
|
function validateIPField(fieldID, errorID, forceValid) {
|
|
return validateField(fieldID, errorID, REGEX_IP, ERROR_IP_VALUE_FORMAT, forceValid);
|
|
}
|
|
|
|
// Validates the given field.
|
|
function validateField(fieldID, errorID, regexPattern, errorMessage, forceValid) {
|
|
// Initialize vars.
|
|
var field = document.getElementById(fieldID);
|
|
var fieldError = document.getElementById(errorID);
|
|
var isValid = true;
|
|
var error = "";
|
|
// Sanity checks.
|
|
if (field == null || fieldError == null)
|
|
return false;
|
|
// Check if value is valid.
|
|
if (!forceValid) {
|
|
var value = field.value;
|
|
if (value.length == 0) {
|
|
isValid = false;
|
|
error = ERROR_FIELD_EMPTY;
|
|
} else if (!value.match(regexPattern)) {
|
|
isValid = false;
|
|
error = errorMessage;
|
|
}
|
|
}
|
|
// Update controls.
|
|
if (isValid) {
|
|
if (field.classList.contains(CLASS_INPUT_ERROR))
|
|
field.classList.remove(CLASS_INPUT_ERROR);
|
|
fieldError.innerHTML = " ";
|
|
fieldError.style.display = "none";
|
|
} else {
|
|
if (!field.classList.contains(CLASS_INPUT_ERROR))
|
|
field.classList.add(CLASS_INPUT_ERROR);
|
|
fieldError.innerHTML = error;
|
|
fieldError.style.display = "block";
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
// Retrieves the given network interface form data.
|
|
function getInterfaceData(element, iface) {
|
|
// Initialize variables.
|
|
var data = {};
|
|
var ifaceData = {};
|
|
var networkData = {};
|
|
var enable = document.getElementById(iface + FIELD_ENABLE).checked;
|
|
var ipMode = document.getElementById(iface + FIELD_IP_MODE).value;
|
|
// Fill network data.
|
|
networkData[ID_ENABLE] = enable;
|
|
if (ipMode == IP_MODE_STATIC) {
|
|
networkData[ID_IP_MODE] = 0;
|
|
networkData[ID_IP] = document.getElementById(iface + FIELD_IP_ADDRESS).value;
|
|
networkData[ID_NETMASK] = document.getElementById(iface + FIELD_SUBNET_MASK).value;
|
|
networkData[ID_GATEWAY] = document.getElementById(iface + FIELD_DEFAULT_GATEWAY).value;
|
|
networkData[ID_DNS1] = document.getElementById(iface + FIELD_DNS1_ADDRESS).value;
|
|
networkData[ID_DNS2] = document.getElementById(iface + FIELD_DNS2_ADDRESS).value;
|
|
} else {
|
|
networkData[ID_IP_MODE] = 1;
|
|
}
|
|
if (iface.startsWith(PREFIX_WIFI)) {
|
|
networkData[ID_SSID] = document.getElementById(iface + FIELD_SSID).value;
|
|
networkData[ID_SECURITY_MODE] = parseInt(document.getElementById(iface + FIELD_ENCRYPTION_TYPE).value);
|
|
if (networkData[ID_SECURITY_MODE] != 0 && passwordChanged)
|
|
networkData[ID_PASSWORD] = document.getElementById(iface + FIELD_PASSWORD).value;
|
|
}
|
|
ifaceData[iface] = networkData;
|
|
data[element] = ifaceData;
|
|
return data;
|
|
}
|
|
|
|
// Saves the network settings for the given interface.
|
|
function saveInterface(element, iface) {
|
|
// Execute only in the network page.
|
|
if (!isNetworkShowing())
|
|
return;
|
|
// Hide the info popup.
|
|
showInfoPopup(false);
|
|
// Show the loading popup.
|
|
showLoadingPopup(true, MESSAGE_SAVING_CONFIGURATION);
|
|
// Send request to set new configuration.
|
|
$.post(
|
|
"http://" + getServerAddress() + "/ajax/set_config",
|
|
JSON.stringify({
|
|
"configuration": getInterfaceData(element, iface)
|
|
}),
|
|
function(data) {
|
|
// Process only in the network page.
|
|
if (!isNetworkShowing())
|
|
return;
|
|
// Process answer.
|
|
processSaveInterfaceResponse(data, element, iface);
|
|
}
|
|
).fail(function(response) {
|
|
// Process only in the network page.
|
|
if (!isNetworkShowing())
|
|
return;
|
|
// Hide the loading panel.
|
|
showLoadingPopup(false);
|
|
// Process error.
|
|
processAjaxErrorResponse(response);
|
|
});
|
|
}
|
|
|
|
// Processes the save network request response.
|
|
function processSaveInterfaceResponse(response, element, iface) {
|
|
// Check for error in the response.
|
|
if (!checkErrorResponse(response, false)) {
|
|
var statusFound = false;
|
|
// Check status in the response.
|
|
if (response[ID_DATA] != null) {
|
|
data = JSON.parse(response[ID_DATA]);
|
|
for (const [elementEntry, elementData] of Object.entries(data)) {
|
|
if (elementEntry != element)
|
|
continue;
|
|
for (const [ifaceEntry, ifaceData] of Object.entries(elementData)) {
|
|
if (ifaceEntry != iface)
|
|
continue;
|
|
if (ifaceData[ID_STATUS] != null) {
|
|
statusFound = true;
|
|
if (ifaceData[ID_STATUS] != 0)
|
|
toastr.error(ifaceData[ID_DESC]);
|
|
else
|
|
toastr.success(MESSAGE_CONFIGURATION_SAVED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!statusFound)
|
|
toastr.error(ERROR_UNKNOWN);
|
|
}
|
|
// Hide the loading panel.
|
|
showLoadingPopup(false);
|
|
}
|
|
|
|
// Toggles the visibility of the given interface panel.
|
|
function togglePanelVisibility(interface) {
|
|
// Initialize variables.
|
|
var panelButton = document.getElementById(interface + ID_TOGGLE_BUTTON);
|
|
// Sanity checks.
|
|
if (panelButton == null)
|
|
return;
|
|
// Check visibility.
|
|
if (panelButton.classList.contains(CLASS_ARROW_UP)) {
|
|
$("#" + interface + ID_PANEL_CONTAINER).slideUp("fast", function() {
|
|
panelButton.classList.remove(CLASS_ARROW_UP);
|
|
panelButton.classList.add(CLASS_ARROW_DOWN);
|
|
});
|
|
} else {
|
|
$("#" + interface + ID_PANEL_CONTAINER).slideDown("fast", function() {
|
|
panelButton.classList.remove(CLASS_ARROW_DOWN);
|
|
panelButton.classList.add(CLASS_ARROW_UP);
|
|
});
|
|
}
|
|
}
|