zoneconf.cgi: Improve the usability of the zone configuration by marking assigned NICs in the zone color. The highlighting is initially applied to the static HTML output, and JavaScript is used to dynamically follow changes made by the user.

Message ID 06299a2d-c302-4712-8a2a-6842ccb3b24b@Leo-Laptop.local
State Superseded
Headers show
Series zoneconf.cgi: Improve the usability of the zone configuration by marking assigned NICs in the zone color. The highlighting is initially applied to the static HTML output, and JavaScript is used to dynamically follow changes made by the user. | expand

Commit Message

Leo-Andres Hofmann Nov. 10, 2020, 12:37 p.m. UTC
Discussion: https://lists.ipfire.org/pipermail/development/2020-October/008567.html

Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
---
 html/cgi-bin/zoneconf.cgi | 89 +++++++++++++++++++++++++++++----------
 1 file changed, 67 insertions(+), 22 deletions(-)

Comments

Daniel Weismüller Nov. 12, 2020, 12:40 p.m. UTC | #1
Hello
Regardless of the advice and improvement requests of others, I have 
applied the patch to my IPFire.
So far everything works as expected and I could not find any errors or 
misbehaviour.

Greetings
Daniel

Am 10.11.2020 um 13:37 schrieb Leo-Andres Hofmann:
> Discussion: https://lists.ipfire.org/pipermail/development/2020-October/008567.html
>
> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
> ---
>   html/cgi-bin/zoneconf.cgi | 89 +++++++++++++++++++++++++++++----------
>   1 file changed, 67 insertions(+), 22 deletions(-)
>
> diff --git a/html/cgi-bin/zoneconf.cgi b/html/cgi-bin/zoneconf.cgi
> index d99a3e611..2501901c4 100644
> --- a/html/cgi-bin/zoneconf.cgi
> +++ b/html/cgi-bin/zoneconf.cgi
> @@ -26,7 +26,7 @@ require '/var/ipfire/general-functions.pl';
>   require "${General::swroot}/lang.pl";
>   require "${General::swroot}/header.pl";
>   
> -my $css = <<END
> +my $extraHead = <<END
>   <style>
>   	table {
>   		width: 100%;
> @@ -64,19 +64,19 @@ my $css = <<END
>   	}
>   
>   	td.green {
> -		background-color: $Header::colourgreen;
> +		background-color: $Header::colourgreen !important;
>   	}
>   
>   	td.red {
> -		background-color: $Header::colourred;
> +		background-color: $Header::colourred !important;
>   	}
>   
>   	td.blue {
> -		background-color: $Header::colourblue;
> +		background-color: $Header::colourblue !important;
>   	}
>   
>   	td.orange {
> -		background-color: $Header::colourorange;
> +		background-color: $Header::colourorange !important;
>   	}
>   
>   	td.topleft {
> @@ -112,6 +112,44 @@ my $css = <<END
>   		margin-top: 1em;
>   	}
>   </style>
> +
> +<script type="text/javascript">
> +	//highlight interface access selection, call this from "onchange" event of selection element
> +	function highlightAccess(selectObj) {
> +		if(!(selectObj && ('zone' in selectObj.dataset) && ('mac' in selectObj.dataset))) {
> +			return; //required parameters are missing
> +		}
> +
> +		var zoneColor = selectObj.dataset.zone.trim().toLowerCase(); //zone color (red/green/blue/orange) CSS class
> +
> +		function colorParentCell(obj, color, enabled = true) { //find nearest parent table cell of "obj" and set its CSS color class
> +			do {
> +				obj = obj.parentElement;
> +			} while(obj && (obj.nodeName.toUpperCase() !== 'TD'));
> +			if(obj && (['green', 'red', 'orange', 'blue'].includes(color))) {
> +				obj.classList.toggle(color, enabled);
> +			}
> +		}
> +
> +		//handle other associated input fields
> +		if(selectObj.type.toUpperCase() === 'RADIO') { //this is a radio button group: clear all highlights
> +			let radios = document.getElementsByName(selectObj.name);
> +			radios.forEach(function(button) {
> +				if(button.nodeName.toUpperCase() === 'INPUT') { //make sure the found element is a button
> +					colorParentCell(button, zoneColor, false); //remove css
> +				}
> +			});
> +		} else { //this is a dropdown menu: enable/disable additional VLAN tag input box
> +			let tagInput = document.getElementById('TAG ' + selectObj.dataset.zone + ' ' + selectObj.dataset.mac); //tag input element selector
> +			if(tagInput) {
> +				tagInput.disabled = (selectObj.value !== 'VLAN'); //enable tag input if VLAN mode selected
> +			}
> +		}
> +
> +		//if interface is assigned, highlight table cell in zone color
> +		colorParentCell(selectObj, zoneColor, (selectObj.value !== 'NONE'));
> +	}
> +</script>
>   END
>   ;
>   
> @@ -162,7 +200,7 @@ foreach (@nics) {
>   	}
>   }
>   
> -&Header::openpage($Lang::tr{"zoneconf title"}, 1, $css);
> +&Header::openpage($Lang::tr{"zoneconf title"}, 1, $extraHead);
>   &Header::openbigbox('100%', 'center');
>   
>   ### Evaluate POST parameters ###
> @@ -312,8 +350,8 @@ if ($cgiparams{"ACTION"} eq $Lang::tr{"save"}) {
>   print <<END
>   <form method='post' enctype='multipart/form-data'>
>   	<table>
> -		<tr>
> -		<td class="h narrow topleft" /td>
> +	<tr>
> +		<td class="h narrow topleft"></td>
>   END
>   ;
>   
> @@ -332,7 +370,7 @@ foreach (@zones) {
>   		my $red_restricted = ($uc eq "RED" && ! ($red_type eq "STATIC" || $red_type eq "DHCP"));
>   
>   		if ($red_restricted) {
> -			print "<td class='h textcenter $_'>$uc ($red_type)</td>";
> +			print "\t\t<td class='h textcenter $_'>$uc ($red_type)</td>\n";
>   
>   			next; # We're done here
>   		}
> @@ -350,7 +388,7 @@ foreach (@zones) {
>   	}
>   
>   	print <<END
> -		<td class='h textcenter $_'>$uc</br>
> +		<td class='h textcenter $_'>$uc<br>
>   			<select name="MODE $uc">
>   				<option value="DEFAULT" $mode_selected{"DEFAULT"}>$Lang::tr{"zoneconf nicmode default"}</option>
>   				<option value="BRIDGE" $mode_selected{"BRIDGE"}>$Lang::tr{"zoneconf nicmode bridge"}</option>
> @@ -361,7 +399,7 @@ END
>   ;
>   }
>   
> -print "</tr>";
> +print "\t</tr>\n";
>   
>   my $slightlygrey = "";
>   
> @@ -370,12 +408,13 @@ foreach (@nics) {
>   	my $nic = $_->[1];
>   	my $wlan = $_->[2];
>   
> -	print "<tr><td class='h narrow textcenter'>$nic<br>$mac</td>";
> +	print "\t<tr>\n\t\t<td class='h narrow textcenter'>$nic<br>$mac</td>\n";
>   
>   	# Iterate through all zones and check if the current NIC is assigned to it
>   	foreach (@zones) {
>   		my $uc = uc $_;
>   		my $dev_name = $ethsettings{"${uc}_DEV"};
> +		my $highlight = "";
>   
>   		if ($dev_name eq "") { # Again, skip the zone if it is not activated
>   			next;
> @@ -391,9 +430,10 @@ foreach (@nics) {
>   
>   				if ($mac eq $ethsettings{"${uc}_MACADDR"}) {
>   					$checked = "checked";
> +					$highlight = $_;
>   				}
>   
> -				print "<td class='textcenter $slightlygrey'><input type='radio' id='PPPACCESS $mac' name='PPPACCESS' value='$mac' $checked></td>";
> +				print "\t\t<td class='textcenter $highlight $slightlygrey'><input type='radio' id='PPPACCESS $mac' name='PPPACCESS' value='$mac' data-zone='RED' data-mac='$mac' onchange='highlightAccess(this)' $checked></td>\n";
>   				next; # We're done here
>   			}
>   		}
> @@ -430,21 +470,26 @@ foreach (@nics) {
>   
>   		$access_selected{"NONE"} = ($access_selected{"NATIVE"} eq "") && ($access_selected{"VLAN"} eq "") ? "selected" : "";
>   		my $vlan_disabled = ($wlan) ? "disabled" : "";
> +		
> +		# If the interface is assigned, hightlight table cell
> +		if ($access_selected{"NONE"} eq "") {
> +			$highlight = $_;
> +		}
>   
>   		print <<END
> -			<td class="textcenter $slightlygrey">
> -				<select name="ACCESS $uc $mac" onchange="document.getElementById('TAG $uc $mac').disabled = (this.value === 'VLAN' ? false : true)">
> -					<option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option>
> -					<option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option>
> -					<option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option>
> -				</select>
> -				<input type="number" class="vlanid" id="TAG $uc $mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled>
> -			</td>
> +		<td class="textcenter $highlight $slightlygrey">
> +			<select name="ACCESS $uc $mac" data-zone="$uc" data-mac="$mac" onchange="highlightAccess(this)">
> +				<option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option>
> +				<option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option>
> +				<option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option>
> +			</select>
> +			<input type="number" class="vlanid" id="TAG $uc $mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled>
> +		</td>
>   END
>   ;
>   	}
>   
> -	print "</tr>";
> +	print "\t</tr>\n";
>   
>   	if ($slightlygrey) {
>   		$slightlygrey = "";

Patch

diff --git a/html/cgi-bin/zoneconf.cgi b/html/cgi-bin/zoneconf.cgi
index d99a3e611..2501901c4 100644
--- a/html/cgi-bin/zoneconf.cgi
+++ b/html/cgi-bin/zoneconf.cgi
@@ -26,7 +26,7 @@  require '/var/ipfire/general-functions.pl';
 require "${General::swroot}/lang.pl";
 require "${General::swroot}/header.pl";
 
-my $css = <<END
+my $extraHead = <<END
 <style>
 	table {
 		width: 100%;
@@ -64,19 +64,19 @@  my $css = <<END
 	}
 
 	td.green {
-		background-color: $Header::colourgreen;
+		background-color: $Header::colourgreen !important;
 	}
 
 	td.red {
-		background-color: $Header::colourred;
+		background-color: $Header::colourred !important;
 	}
 
 	td.blue {
-		background-color: $Header::colourblue;
+		background-color: $Header::colourblue !important;
 	}
 
 	td.orange {
-		background-color: $Header::colourorange;
+		background-color: $Header::colourorange !important;
 	}
 
 	td.topleft {
@@ -112,6 +112,44 @@  my $css = <<END
 		margin-top: 1em;
 	}
 </style>
+
+<script type="text/javascript">
+	//highlight interface access selection, call this from "onchange" event of selection element
+	function highlightAccess(selectObj) {
+		if(!(selectObj && ('zone' in selectObj.dataset) && ('mac' in selectObj.dataset))) {
+			return; //required parameters are missing
+		}
+
+		var zoneColor = selectObj.dataset.zone.trim().toLowerCase(); //zone color (red/green/blue/orange) CSS class
+
+		function colorParentCell(obj, color, enabled = true) { //find nearest parent table cell of "obj" and set its CSS color class
+			do {
+				obj = obj.parentElement;
+			} while(obj && (obj.nodeName.toUpperCase() !== 'TD'));
+			if(obj && (['green', 'red', 'orange', 'blue'].includes(color))) {
+				obj.classList.toggle(color, enabled);
+			}
+		}
+
+		//handle other associated input fields
+		if(selectObj.type.toUpperCase() === 'RADIO') { //this is a radio button group: clear all highlights
+			let radios = document.getElementsByName(selectObj.name);
+			radios.forEach(function(button) {
+				if(button.nodeName.toUpperCase() === 'INPUT') { //make sure the found element is a button
+					colorParentCell(button, zoneColor, false); //remove css
+				}
+			});
+		} else { //this is a dropdown menu: enable/disable additional VLAN tag input box
+			let tagInput = document.getElementById('TAG ' + selectObj.dataset.zone + ' ' + selectObj.dataset.mac); //tag input element selector
+			if(tagInput) {
+				tagInput.disabled = (selectObj.value !== 'VLAN'); //enable tag input if VLAN mode selected
+			}
+		}
+
+		//if interface is assigned, highlight table cell in zone color
+		colorParentCell(selectObj, zoneColor, (selectObj.value !== 'NONE'));
+	}
+</script>
 END
 ;
 
@@ -162,7 +200,7 @@  foreach (@nics) {
 	}
 }
 
-&Header::openpage($Lang::tr{"zoneconf title"}, 1, $css);
+&Header::openpage($Lang::tr{"zoneconf title"}, 1, $extraHead);
 &Header::openbigbox('100%', 'center');
 
 ### Evaluate POST parameters ###
@@ -312,8 +350,8 @@  if ($cgiparams{"ACTION"} eq $Lang::tr{"save"}) {
 print <<END
 <form method='post' enctype='multipart/form-data'>
 	<table>
-		<tr>
-		<td class="h narrow topleft" /td>
+	<tr>
+		<td class="h narrow topleft"></td>
 END
 ;
 
@@ -332,7 +370,7 @@  foreach (@zones) {
 		my $red_restricted = ($uc eq "RED" && ! ($red_type eq "STATIC" || $red_type eq "DHCP"));
 
 		if ($red_restricted) {
-			print "<td class='h textcenter $_'>$uc ($red_type)</td>";
+			print "\t\t<td class='h textcenter $_'>$uc ($red_type)</td>\n";
 
 			next; # We're done here
 		}
@@ -350,7 +388,7 @@  foreach (@zones) {
 	}
 
 	print <<END
-		<td class='h textcenter $_'>$uc</br>
+		<td class='h textcenter $_'>$uc<br>
 			<select name="MODE $uc">
 				<option value="DEFAULT" $mode_selected{"DEFAULT"}>$Lang::tr{"zoneconf nicmode default"}</option>
 				<option value="BRIDGE" $mode_selected{"BRIDGE"}>$Lang::tr{"zoneconf nicmode bridge"}</option>
@@ -361,7 +399,7 @@  END
 ;
 }
 
-print "</tr>";
+print "\t</tr>\n";
 
 my $slightlygrey = "";
 
@@ -370,12 +408,13 @@  foreach (@nics) {
 	my $nic = $_->[1];
 	my $wlan = $_->[2];
 
-	print "<tr><td class='h narrow textcenter'>$nic<br>$mac</td>";
+	print "\t<tr>\n\t\t<td class='h narrow textcenter'>$nic<br>$mac</td>\n";
 
 	# Iterate through all zones and check if the current NIC is assigned to it
 	foreach (@zones) {
 		my $uc = uc $_;
 		my $dev_name = $ethsettings{"${uc}_DEV"};
+		my $highlight = "";
 
 		if ($dev_name eq "") { # Again, skip the zone if it is not activated
 			next;
@@ -391,9 +430,10 @@  foreach (@nics) {
 
 				if ($mac eq $ethsettings{"${uc}_MACADDR"}) {
 					$checked = "checked";
+					$highlight = $_;
 				}
 
-				print "<td class='textcenter $slightlygrey'><input type='radio' id='PPPACCESS $mac' name='PPPACCESS' value='$mac' $checked></td>";
+				print "\t\t<td class='textcenter $highlight $slightlygrey'><input type='radio' id='PPPACCESS $mac' name='PPPACCESS' value='$mac' data-zone='RED' data-mac='$mac' onchange='highlightAccess(this)' $checked></td>\n";
 				next; # We're done here
 			}
 		}
@@ -430,21 +470,26 @@  foreach (@nics) {
 
 		$access_selected{"NONE"} = ($access_selected{"NATIVE"} eq "") && ($access_selected{"VLAN"} eq "") ? "selected" : "";
 		my $vlan_disabled = ($wlan) ? "disabled" : "";
+		
+		# If the interface is assigned, hightlight table cell
+		if ($access_selected{"NONE"} eq "") {
+			$highlight = $_;
+		}
 
 		print <<END
-			<td class="textcenter $slightlygrey">
-				<select name="ACCESS $uc $mac" onchange="document.getElementById('TAG $uc $mac').disabled = (this.value === 'VLAN' ? false : true)">
-					<option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option>
-					<option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option>
-					<option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option>
-				</select>
-				<input type="number" class="vlanid" id="TAG $uc $mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled>
-			</td>
+		<td class="textcenter $highlight $slightlygrey">
+			<select name="ACCESS $uc $mac" data-zone="$uc" data-mac="$mac" onchange="highlightAccess(this)">
+				<option value="NONE" $access_selected{"NONE"}>- $Lang::tr{"zoneconf access none"} -</option>
+				<option value="NATIVE" $access_selected{"NATIVE"}>$Lang::tr{"zoneconf access native"}</option>
+				<option value="VLAN" $access_selected{"VLAN"} $vlan_disabled>$Lang::tr{"zoneconf access vlan"}</option>
+			</select>
+			<input type="number" class="vlanid" id="TAG $uc $mac" name="TAG $uc $mac" min="1" max="4095" value="$zone_vlan_id" $field_disabled>
+		</td>
 END
 ;
 	}
 
-	print "</tr>";
+	print "\t</tr>\n";
 
 	if ($slightlygrey) {
 		$slightlygrey = "";