From dc8d739eaa40696867dff5751f66b299709521f4 Mon Sep 17 00:00:00 2001
From: Nigel Kukard <nkukard@lbsd.net>
Date: Fri, 20 Sep 2013 20:25:23 +0000
Subject: [PATCH] Major interface +code reworking & cosmetic fixes

---
 .../plugins/webserver/pages/configmanager.pm  | 340 ++++++++++++------
 .../plugins/webserver/pages/limits.pm         | 298 +++++++++++----
 .../plugins/webserver/webserver.pm            |   8 +-
 3 files changed, 470 insertions(+), 176 deletions(-)

diff --git a/opentrafficshaper/plugins/webserver/pages/configmanager.pm b/opentrafficshaper/plugins/webserver/pages/configmanager.pm
index 7a35700..0baa423 100644
--- a/opentrafficshaper/plugins/webserver/pages/configmanager.pm
+++ b/opentrafficshaper/plugins/webserver/pages/configmanager.pm
@@ -1,6 +1,6 @@
 # OpenTrafficShaper webserver module: configmanager page
 # Copyright (C) 2007-2013, AllWorldIT
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -37,7 +37,7 @@ use URI::Escape;
 
 use opentrafficshaper::logger;
 use opentrafficshaper::plugins;
-use opentrafficshaper::utils qw( parseFormContent isUsername isIP isNumber prettyUndef );
+use opentrafficshaper::utils qw( parseFormContent parseURIQuery isUsername isIP isNumber prettyUndef );
 
 use opentrafficshaper::plugins::configmanager qw( getOverrides getOverride getTrafficClasses getTrafficClassName isTrafficClassValid );
 
@@ -49,7 +49,7 @@ my $menu = {
 		'All Overrides' => '',
 	},
 	'Admin' =>  {
-		'Add Override' => 'add',
+		'Add Override' => 'override-add',
 	},
 };
 
@@ -95,12 +95,12 @@ EOF
 			next;
 		}
 
-		my $keyEscaped = uri_escape($override->{'Key'});
+		my $idEscaped = uri_escape($override->{'ID'});
 
 		my $friendlyNameEncoded = prettyUndef(encode_entities($override->{'FriendlyName'}));
 		my $usernameEncoded = prettyUndef(encode_entities($override->{'Username'}));
 		my $ipAddress = prettyUndef($override->{'IP'});
-		my $expiresStr = DateTime->from_epoch( epoch => $override->{'Expires'} )->iso8601();
+		my $expiresStr = ($override->{'Expires'} > 0) ? DateTime->from_epoch( epoch => $override->{'Expires'} )->iso8601() : '-never-';
 
 		my $classStr = prettyUndef(getTrafficClassName($override->{'ClassID'}));
 		my $cirStr = sprintf('%s/%s',prettyUndef($override->{'TrafficLimitTx'}),prettyUndef($override->{'TrafficLimitRx'}));
@@ -120,8 +120,8 @@ EOF
 			<td>$cirStr</td>
 			<td>$limitStr</td>
 			<td>
-				<a href="/configmanager/override-edit?key=$keyEscaped"><span class="glyphicon glyphicon-wrench" /></a>
-				<a href="/configmanager/override-remove?key=$keyEscaped"><span class="glyphicon glyphicon-remove" /></a>
+				<a href="/configmanager/override-edit?oid=$idEscaped"><span class="glyphicon glyphicon-wrench" /></a>
+				<a href="/configmanager/override-remove?oid=$idEscaped"><span class="glyphicon glyphicon-remove" /></a>
 			</td>
 		</tr>
 EOF
@@ -147,8 +147,8 @@ EOF
 }
 
 
-# Add action
-sub add
+# Add/edit action
+sub override_addedit
 {
 	my ($kernel,$globals,$client_session_id,$request) = @_;
 
@@ -156,85 +156,135 @@ sub add
 	# Setup our environment
 	my $logger = $globals->{'logger'};
 
-	# Errors to display
+	# Errors to display above the form
 	my @errors;
-	# Form items
-	my $params = {
-		'inputFriendlyName' => undef,
-		'inputUsername' => undef,
-		'inputIP' => undef,
-		'inputTrafficClass' => undef,
-		'inputTrafficClassEnabled' => undef,
-		'inputLimitTx' => undef,
-		'inputLimitTxEnabled' => undef,
-		'inputLimitTxBurst' => undef,
-		'inputLimitTxBurstEnabled' => undef,
-		'inputLimitRx' => undef,
-		'inputLimitRxEnabled' => undef,
-		'inputLimitRxBurst' => undef,
-		'inputLimitRxBurstEnabled' => undef,
-		'inputExpires' => undef,
-		'inputExpiresModifier' => undef,
-		'inputNotes' => undef,
-	};
+
+	# Items for our form...
+	my @formElements = qw(
+		FriendlyName
+		Username IP
+		ClassID
+		TrafficLimitTx TrafficLimitTxBurst
+		TrafficLimitRx TrafficLimitRxBurst
+		Expires inputExpires.modifier
+		Notes
+	);
+	my @formElementCheckboxes = qw(
+		ClassID
+		TrafficLimitTx TrafficLimitTxBurst
+		TrafficLimitRx TrafficLimitRxBurst
+	);
+
+	# Title of the form, by default its an add form
+	my $formType = "Add";
+	my $formNoEdit = "";
+	# Form data
+	my $formData;
+
+	#
+	# Here is where we going to try load data we already have
+	#
 
 	# If this is a form try parse it
 	if ($request->method eq "POST") {
 		# Parse form data
-		$params = parseFormContent($request->content);
+		$formData = parseFormContent($request->content);
 
 		# If user pressed cancel, redirect
-		if (defined($params->{'cancel'})) {
+		if (defined($formData->{'cancel'})) {
 			# Redirects to default page
-			return (HTTP_TEMPORARY_REDIRECT,'limits');
+			return (HTTP_TEMPORARY_REDIRECT,'configmanager');
 		}
 
-		# Check POST data
-		my $friendlyName = $params->{'inputFriendlyName'};
+	# Maybe we were given an override key as a parameter? this would be an edit form
+	} elsif ($request->method eq "GET") {
+		# Parse GET data
+		my $queryParams = parseURIQuery($request);
+		# We need a ID first of all...
+		if (defined($queryParams->{'oid'})) {
+			# Check if we get some data back when pulling the override from the backend
+			if (defined($formData = getOverride($queryParams->{'oid'}))) {
+				# Setup our checkboxes
+				foreach my $checkbox (@formElementCheckboxes) {
+					if (defined($formData->{$checkbox})) {
+						$formData->{"input$checkbox.enabled"} = "on";
+					}
+				}
+
+				# Work out expires modifier
+				# XXX - TODO
+			# If we didn't get any data, then something went wrong
+			} else {
+				my $encodedID = encode_entities($queryParams->{'oid'});
+				push(@errors,"Override data could not be loaded using oid '$encodedID'");
+			}
+			# Lastly if we were given a oid, this is actually an edit
+			$formType = "Edit";
+			$formNoEdit = "readonly";
+
+		# Woops ... no query string?
+		} elsif (%{$queryParams} > 0) {
+			push(@errors,"No override oid in query string!");
+			$formType = "Edit";
+			$formNoEdit = "readonly";
+		}
+	}
+
+
+	#
+	# If we already have data, lets check how valid it is...
+	#
+
+	# We only do this if we have hash elements
+	if (ref($formData) eq "HASH") {
+		my $friendlyName = $formData->{'FriendlyName'};
+		if (!defined($friendlyName)) {
+			push(@errors,"Friendly name must be specified");
+		}
 
 		# Make sure we have at least the username or IP
-		my $username = isUsername($params->{'inputUsername'});
-		my $ipAddress = isIP($params->{'inputIP'});
+		my $username = isUsername($formData->{'Username'});
+		my $ipAddress = isIP($formData->{'IP'});
 		if (!defined($username) && !defined($ipAddress)) {
 			push(@errors,"IP Address and/or Username must be specified");
 		}
 
 		# If the traffic class is ticked, process it
-		my $trafficClass;
-		if (defined($params->{'inputTrafficClassEnabled'})) {
-			if (!defined($trafficClass = isTrafficClassValid($params->{'inputTrafficClass'}))) {
+		my $classID;
+		if (defined($formData->{'inputClassID.enabled'})) {
+			if (!defined($classID = isTrafficClassValid($formData->{'ClassID'}))) {
 				push(@errors,"Traffic class is not valid");
 			}
 		}
-		# Check TrafficLimitTx
+		# Check traffic limits
 		my $trafficLimitTx;
-		if (defined($params->{'inputTrafficLimitTxEnabled'})) {
-			if (!defined($trafficLimitTx = isNumber($params->{'inputLimitTx'}))) {
+		if (defined($formData->{'inputTrafficLimitTx.enabled'})) {
+			if (!defined($trafficLimitTx = isNumber($formData->{'TrafficLimitTx'}))) {
 				push(@errors,"Download CIR is not valid");
 			}
 		}
 		my $trafficLimitTxBurst;
-		if (defined($params->{'inputTrafficLimitTxBurstEnabled'})) {
-			if (!defined($trafficLimitTxBurst = isNumber($params->{'inputLimitTxBurst'}))) {
+		if (defined($formData->{'inputTrafficLimitTxBurst.enabled'})) {
+			if (!defined($trafficLimitTxBurst = isNumber($formData->{'TrafficLimitTxBurst'}))) {
 				push(@errors,"Download limit is not valid");
 			}
 		}
 		# Check TrafficLimitRx
 		my $trafficLimitRx;
-		if (defined($params->{'inputTrafficLimitRxEnabled'})) {
-			if (!defined($trafficLimitRx = isNumber($params->{'inputLimitRx'}))) {
+		if (defined($formData->{'inputTrafficLimitRx.enabled'})) {
+			if (!defined($trafficLimitRx = isNumber($formData->{'TrafficLimitRx'}))) {
 				push(@errors,"Upload CIR is not valid");
 			}
 		}
 		my $trafficLimitRxBurst;
-		if (defined($params->{'inputTrafficLimitRxBurstEnabled'})) {
-			if (!defined($trafficLimitRxBurst = isNumber($params->{'inputLimitRxBurst'}))) {
+		if (defined($formData->{'inputTrafficLimitRxBurst.enabled'})) {
+			if (!defined($trafficLimitRxBurst = isNumber($formData->{'TrafficLimitRxBurst'}))) {
 				push(@errors,"Upload limit is not valid");
 			}
 		}
 		# Check that we actually have something to override
 		if (
-				!defined($trafficClass) && 
+				!defined($classID) &&
 				!defined($trafficLimitTx) && !defined($trafficLimitTxBurst) &&
 				!defined($trafficLimitRx) && !defined($trafficLimitRxBurst)
 		) {
@@ -242,43 +292,48 @@ sub add
 		}
 
 		my $expires = 0;
-		if (defined($params->{'inputExpires'}) && $params->{'inputExpires'} ne "") {
-			if (!defined($expires = isNumber($params->{'inputExpires'}))) {
+		if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
+			if (!defined($expires = isNumber($formData->{'Expires'}))) {
 				push(@errors,"Expires value is not valid");
 			# Check the modifier
 			} else {
 				# Check if its defined
-				if (defined($params->{'inputExpiresModifier'}) && $params->{'inputExpiresModifier'} ne "") {
+				if (defined($formData->{'inputExpires.modifier'}) && $formData->{'inputExpires.modifier'} ne "") {
 					# Minutes
-					if ($params->{'inputExpiresModifier'} eq "m") {
+					if ($formData->{'inputExpires.modifier'} eq "m") {
 						$expires *= 60;
 					# Hours
-					} elsif ($params->{'inputExpiresModifier'} eq "h") {
+					} elsif ($formData->{'inputExpires.modifier'} eq "h") {
 						$expires *= 3600;
 					# Days
-					} elsif ($params->{'inputExpiresModifier'} eq "d") {
+					} elsif ($formData->{'inputExpires.modifier'} eq "d") {
 						$expires *= 86400;
 					} else {
 						push(@errors,"Expires modifier is not valid");
 					}
 				}
-				# Set right time for expiry
-				$expires += time();
+				# Base the expiry off now, plus the expiry time
+				if ($expires > 0) {
+					$expires += time();
+				}
 			}
 		}
 		# Grab notes
-		my $notes = $params->{'inputNotes'};
+		my $notes = $formData->{'Notes'};
 
+		#
+		# Process change if this is a POST and there are no errors
+		#
 
 		# If there are no errors we need to push this override
-		if (!@errors) {
+		if (!@errors && $request->method eq "POST") {
 			# Build override
 			my $override = {
 				'FriendlyName' => $friendlyName,
 				'Username' => $username,
 				'IP' => $ipAddress,
 				'GroupID' => 1,
-				'ClassID' => $trafficClass,
+				'ClassID' => $classID,
 				'TrafficLimitTx' => $trafficLimitTx,
 				'TrafficLimitTxBurst' => $trafficLimitTxBurst,
 				'TrafficLimitRx' => $trafficLimitRx,
@@ -295,7 +350,7 @@ sub add
 					prettyUndef($username),
 					prettyUndef($ipAddress),
 					"",
-					prettyUndef($trafficClass),
+					prettyUndef($classID),
 					prettyUndef($trafficLimitTx),
 					prettyUndef($trafficLimitRx),
 					prettyUndef($trafficLimitTxBurst),
@@ -306,26 +361,29 @@ sub add
 		}
 	}
 
+
+	#
+	# Sanitize all data we going to be using
+	#
+
 	# Handle checkboxes first and a little differently
-	foreach my $item (
-			"inputTrafficClassEnabled",
-			"inputLimitTxEnabled","inputLimitTxBurstEnabled",
-			"inputLimitRxEnabled", "inputLimitRxBurstEnabled"
-	) {
-		$params->{$item} = defined($params->{$item}) ? "checked" : "";	
+	foreach my $item (@formElementCheckboxes) {
+		$formData->{"input$item.enabled"} = defined($formData->{"input$item.enabled"}) ? "checked" : "";
 	}
 	# Sanitize params if we need to
-	foreach my $item (keys %{$params}) {
-		$params->{$item} = defined($params->{$item}) ? encode_entities($params->{$item}) : "";
+	foreach my $item (@formElements) {
+		$formData->{$item} = defined($formData->{$item}) ? encode_entities($formData->{$item}) : "";
 	}
 
 	# Build content
 	my $content = "";
 
+	#
 	# Form header
+	#
 	$content .=<<EOF;
 <form role="form" method="post">
-	<legend>Add Override</legend>
+	<legend>$formType Override</legend>
 EOF
 
 	# Spit out errors if we have any
@@ -338,54 +396,63 @@ EOF
 	# Generate traffic class list
 	my $trafficClasses = getTrafficClasses();
 	my $trafficClassStr = "";
-	foreach my $classID (keys %{$trafficClasses}) {
+	foreach my $classID (sort keys %{$trafficClasses}) {
 		# Process selections nicely
 		my $selected = "";
-		if ($params->{'inputTrafficClass'} ne "" && $params->{'inputTrafficClass'} eq $classID) {
+		if ($formData->{'ClassID'} ne "" && $formData->{'ClassID'} eq $classID) {
 			$selected = "selected";
 		}
 		# And build the options
 		$trafficClassStr .= '<option value="'.$classID.'" '.$selected.'>'.$trafficClasses->{$classID}.'</option>';
 	}
 
-	# Header
+	# Make expires look nicer
+	my $expiresStr = "";
+	if (defined($formData->{'Expires'}) && $formData->{'Expires'} > 0) {
+		$expiresStr = $formData->{'Expires'};
+	}
+
+
+	#
+	# Page content
+	#
 	$content .=<<EOF;
 	<div class="form-group">
-		<label for="inputFriendlyName" class="col-lg-2 control-label">FriendlyName</label>
+		<label for="FriendlyName" class="col-lg-2 control-label">FriendlyName</label>
 		<div class="row">
 			<div class="col-lg-4">
 				<div class="input-group">
-					<input name="inputFriendlyName" type="text" placeholder="Friendly Name" class="form-control" value="$params->{'inputFriendlyName'}" />
+					<input name="FriendlyName" type="text" placeholder="Friendly Name" class="form-control" value="$formData->{'FriendlyName'}" />
 					<span class="input-group-addon">*</span>
 				</div>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputUsername" class="col-lg-2 control-label">Username</label>
+		<label for="Username" class="col-lg-2 control-label">Username</label>
 		<div class="row">
 			<div class="col-lg-4">
-				<input name="inputUsername" type="text" placeholder="Username To Override" class="form-control" value="$params->{'inputUsername'}" />
+				<input name="Username" type="text" placeholder="Username To Override" class="form-control" value="$formData->{'Username'}" $formNoEdit/>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputIP" class="col-lg-2 control-label">IP Address</label>
+		<label for="IP" class="col-lg-2 control-label">IP Address</label>
 		<div class="row">
 			<div class="col-lg-4">
-				<input name="inputIP" type="text" placeholder="And/Or IP Address To Override" class="form-control" value="$params->{'inputIP'}" />
+				<input name="IP" type="text" placeholder="And/Or IP Address To Override" class="form-control" value="$formData->{'IP'}" $formNoEdit/>
 			</div>
 		</div>
 	</div>
 
 	<div class="form-group">
-		<label for="inputTafficClass" class="col-lg-2 control-label">Traffic Class</label>
+		<label for="ClassID" class="col-lg-2 control-label">Traffic Class</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputTrafficClassEnabled" type="checkbox" $params->{'inputTrafficClassEnabled'}/> Override
+				<input name="inputClassID.enabled" type="checkbox" $formData->{'inputClassID.enabled'}/> Override
 			</div>
 			<div class="col-lg-2">
-				<select name="inputTrafficClass" placeholder="Traffic Class" class="form-control" value="$params->{'inputTrafficClass'}">
+				<select name="ClassID" class="form-control">
 					$trafficClassStr
 				</select>
 			</div>
@@ -393,15 +460,15 @@ EOF
 	</div>
 
 	<div class="form-group">
-		<label for="inputLimitTx" class="col-lg-2 control-label">Download CIR</label>
+		<label for="TrafficLimitTx" class="col-lg-2 control-label">Download CIR</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputLimitTxEnabled" type="checkbox" $params->{'inputLimitTxEnabled'}/> Override
+				<input name="inputTrafficLimitTx.enabled" type="checkbox" $formData->{'inputTrafficLimitTx.enabled'}/> Override
 			</div>
 
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitTx" type="text" placeholder="Download CIR" class="form-control" value="$params->{'inputLimitTx'}" />
+					<input name="TrafficLimitTx" type="text" placeholder="Download CIR" class="form-control" value="$formData->{'TrafficLimitTx'}" />
 					<span class="input-group-addon">Kbps<span>
 				</div>
 			</div>
@@ -409,14 +476,14 @@ EOF
 	</div>
 
 	<div class="form-group">
-		<label for="inputLimitTxBurst" class="col-lg-2 control-label">Download Limit</label>
+		<label for="TrafficLimitTxBurst" class="col-lg-2 control-label">Download Limit</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputLimitTxBurstEnabled" type="checkbox" $params->{'inputLimitTxBurstEnabled'}/> Override
+				<input name="inputTrafficLimitTxBurst.enabled" type="checkbox" $formData->{'inputTrafficLimitTxBurst.enabled'}/> Override
 			</div>
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitTxBurst" type="text" placeholder="Download Limit" class="form-control" value="$params->{'inputLimitTxBurst'}" />
+					<input name="TrafficLimitTxBurst" type="text" placeholder="Download Limit" class="form-control" value="$formData->{'TrafficLimitTxBurst'}" />
 					<span class="input-group-addon">Kbps<span>
 				</div>
 			</div>
@@ -424,28 +491,28 @@ EOF
 	</div>
 
 	<div class="form-group">
-		<label for="inputLimitRx" class="col-lg-2 control-label">Upload CIR</label>
+		<label for="inputTrafficLimitRx" class="col-lg-2 control-label">Upload CIR</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputLimitRxEnabled" type="checkbox" $params->{'inputLimitRxEnabled'}/> Override
+				<input name="inputTrafficLimitRx.enabled" type="checkbox" $formData->{'inputTrafficLimitRx.enabled'}/> Override
 			</div>
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitRx" type="text" placeholder="Upload CIR" class="form-control" value="$params->{'inputLimitRx'}" />
+					<input name="TrafficLimitRx" type="text" placeholder="Upload CIR" class="form-control" value="$formData->{'TrafficLimitRx'}" />
 					<span class="input-group-addon">Kbps<span>
 				</div>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputLimitRxBurst" class="col-lg-2 control-label">Upload Limit</label>
+		<label for="TrafficLimitRxBurst" class="col-lg-2 control-label">Upload Limit</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputLimitRxBurstEnabled" type="checkbox" $params->{'inputLimitRxBurstEnabled'}/> Override
+				<input name="inputTrafficLimitRxBurst.enabled" type="checkbox" $formData->{'inputTrafficLimitRxBurst.enabled'}/> Override
 			</div>
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitRxBurst" type="text" placeholder="Upload Limit" class="form-control" value="$params->{'inputLimitRxBurst'}" />
+					<input name="TrafficLimitRxBurst" type="text" placeholder="Upload Limit" class="form-control" value="$formData->{'TrafficLimitRxBurst'}" />
 					<span class="input-group-addon">Kbps<span>
 				</div>
 			</div>
@@ -453,13 +520,13 @@ EOF
 	</div>
 
 	<div class="form-group">
-		<label for="inputExpires" class="col-lg-2 control-label">Expires</label>
+		<label for="Expires" class="col-lg-2 control-label">Expires</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputExpires" type="text" placeholder="Expires" class="form-control" value="$params->{'inputExpires'}" />
+				<input name="Expires" type="text" placeholder="Expires" class="form-control" value="$expiresStr" />
 			</div>
 			<div class="col-lg-2">
-				<select name="inputExpiresModifier" placeholder="Expires Modifier" class="form-control" value="$params->{'inputExpiresModifier'}">
+				<select name="inputExpires.modifier" class="form-control" value="$formData->{'inputExpires.modifier'}">
 					<option value="m">Mins</option>
 					<option value="h">Hours</option>
 					<option value="d">Days</option>
@@ -469,15 +536,15 @@ EOF
 	</div>
 
 	<div class="form-group">
-		<label for="inputNotes" class="col-lg-2 control-label">Notes</label>
+		<label for="Notes" class="col-lg-2 control-label">Notes</label>
 		<div class="row">
 			<div class="col-lg-4">
-				<textarea name="inputNotes" placeholder="Notes" rows="3" class="form-control"></textarea>
+				<textarea name="Notes" placeholder="Notes" rows="3" class="form-control"></textarea>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<button type="submit" class="btn btn-primary">Add</button>
+		<button type="submit" class="btn btn-primary">$formType</button>
 		<button name="cancel" type="submit" class="btn">Cancel</button>
 	</div>
 </form>
@@ -487,5 +554,78 @@ EOF
 }
 
 
+# Remove action
+sub override_remove
+{
+	my ($kernel,$globals,$client_session_id,$request) = @_;
+
+
+	# Content to return
+	my $content = "";
+
+
+	# Pull in GET
+	my $queryParams = parseURIQuery($request);
+	# We need a key first of all...
+	if (!defined($queryParams->{'oid'})) {
+		$content = <<EOF;
+			<div class="alert alert-danger text-center">
+				No override oid in query string!
+			</div>
+EOF
+		goto END;
+	}
+
+	# Grab the override
+	my $override = getOverride($queryParams->{'oid'});
+
+	# Make the oid safe for HTML
+	my $encodedID = encode_entities($queryParams->{'oid'});
+
+	# Make sure the oid was valid... we would have an override now if it was
+	if (!defined($override)) {
+		$content = <<EOF;
+			<div class="alert alert-danger text-center">
+				Invalid override oid "$encodedID"!
+			</div>
+EOF
+		goto END;
+	}
+
+	# Pull in POST
+	my $postParams = parseFormContent($request->content);
+	# If this is a post, then its probably a confirmation
+	if (defined($postParams->{'confirm'})) {
+		# Check if its a success
+		if ($postParams->{'confirm'} eq "Yes") {
+			# Post the removal
+			$kernel->post("configmanager" => "process_override_remove" => $override);
+		}
+		return (HTTP_TEMPORARY_REDIRECT,'configmanager');
+	}
+
+
+	# Make the friendly name HTML safe
+	my $encodedFriendlyName = encode_entities($override->{'FriendlyName'});
+
+	# Build our confirmation dialog
+	$content .= <<EOF;
+		<div class="alert alert-danger">
+			Are you very sure you wish to remove override "$encodedFriendlyName"?
+		</div>
+		<form role="form" method="post">
+			<input type="submit" class="btn btn-primary" name="confirm" value="Yes" />
+			<input type="submit" class="btn btn-default" name="confirm" value="No" />
+		</form>
+EOF
+	# And here is where we return
+END:
+	return (HTTP_OK,$content,{ 'menu' => $menu });
+}
+
+
+
+
+
 1;
 # vim: ts=4
diff --git a/opentrafficshaper/plugins/webserver/pages/limits.pm b/opentrafficshaper/plugins/webserver/pages/limits.pm
index 6f5243b..39a42c1 100644
--- a/opentrafficshaper/plugins/webserver/pages/limits.pm
+++ b/opentrafficshaper/plugins/webserver/pages/limits.pm
@@ -1,6 +1,6 @@
 # OpenTrafficShaper webserver module: limits page
 # Copyright (C) 2007-2013, AllWorldIT
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -51,7 +51,7 @@ my $menu = {
 		'Manual Limits' => './?source=plugin.webserver.limits',
 	},
 	'Admin' => {
-		'Add Limit' => 'add',
+		'Add Limit' => 'limit-add',
 	},
 };
 
@@ -144,11 +144,19 @@ EOF
 			}
 		}
 
+		my $lidEncoded = encode_entities($limit->{'ID'});
+
 		my $usernameEncoded = encode_entities($limit->{'Username'});
 		my $usernameEscaped = uri_escape($limit->{'Username'});
 
 		my $classStr = getTrafficClassName($limit->{'ClassID'});
 
+		# We only support removing certain sources of limits
+		my $removeLink = "";
+		if ($limit->{'Source'} eq "plugin.webserver.limits") {
+			$removeLink = "<a href=\"/limits/limit-remove?lid=$lidEncoded\"><span class=\"glyphicon glyphicon-remove\"></span></a>";
+		}
+
 		$content .= <<EOF;
 		<tr class="$style">
 			<td>$icon</td>
@@ -194,8 +202,8 @@ EOF
 			<td>$limitStr</td>
 			<td>
 				<a href="/statistics/by-username?username=$usernameEscaped"><span class="glyphicon glyphicon-stats"></span></a>
-				<a href="/limits/limit-edit?username=$usernameEscaped"><span class="glyphicon glyphicon-wrench"></span></a>
-				<a href="/limits/limit-remove?username=$usernameEscaped"><span class="glyphicon glyphicon-remove"></span></a>
+				<a href="/limits/limit-edit?lid=$lidEncoded"><span class="glyphicon glyphicon-wrench"></span></a>
+				$removeLink
 			</td>
 		</tr>
 EOF
@@ -246,8 +254,8 @@ EOF
 }
 
 
-# Add action
-sub add
+# Add/edit action
+sub limit_addedit
 {
 	my ($kernel,$globals,$client_session_id,$request) = @_;
 
@@ -255,115 +263,164 @@ sub add
 	# Setup our environment
 	my $logger = $globals->{'logger'};
 
-	# Errors to display
+	# Errors to display above the form
 	my @errors;
-	# Form items
-	my $params = {
-		'inputFriendlyName' => undef,
-		'inputUsername' => undef,
-		'inputIP' => undef,
-		'inputTrafficClass' => undef,
-		'inputLimitTx' => undef,
-		'inputLimitTxBurst' => undef,
-		'inputLimitRx' => undef,
-		'inputLimitRxBurst' => undef,
-		'inputExpires' => undef,
-		'inputExpiresModifier' => undef,
-		'inputNotes' => undef,
-	};
+
+	# Items for our form...
+	my @formElements = qw(
+		FriendlyName
+		Username IP
+		ClassID
+		TrafficLimitTx TrafficLimitTxBurst
+		TrafficLimitRx TrafficLimitRxBurst
+		Expires inputExpires.modifier
+		Notes
+	);
+
+	# Title of the form, by default its an add form
+	my $formType = "Add";
+	my $formNoEdit = "";
+	# Form data
+	my $formData;
 
 	# If this is a form try parse it
 	if ($request->method eq "POST") {
 		# Parse form data
-		$params = parseFormContent($request->content);
+		$formData = parseFormContent($request->content);
 
 		# If user pressed cancel, redirect
-		if (defined($params->{'cancel'})) {
+		if (defined($formData->{'cancel'})) {
 			# Redirects to default page
 			return (HTTP_TEMPORARY_REDIRECT,'limits');
 		}
 
+	# Maybe we were given an override key as a parameter? this would be an edit form
+	} elsif ($request->method eq "GET") {
+		# Parse GET data
+		my $queryParams = parseURIQuery($request);
+		# We need a key first of all...
+		if (defined($queryParams->{'lid'})) {
+			# Check if we get some data back when pulling the limit from the backend
+			if (defined($formData = getLimit($queryParams->{'lid'}))) {
+				# We need to make sure we're only editing our own limits
+				if ($formData->{'Source'} ne "plugin.webserver.limits") {
+					return (HTTP_TEMPORARY_REDIRECT,'limits');
+				}
+
+				# Work out expires modifier
+# XXX - TODO
+			# If we didn't get any data, then something went wrong
+			} else {
+				my $encodedID = encode_entities($queryParams->{'lid'});
+				push(@errors,"Limit data could not be loaded using limit ID '$encodedID'");
+			}
+			# Lastly if we were given a key, this is actually an edit
+			$formType = "Edit";
+			$formNoEdit = "readonly";
+
+		# Woops ... no query string?
+		} elsif (%{$queryParams} > 0) {
+			push(@errors,"No limit ID in query string!");
+			$formType = "Edit";
+			$formNoEdit = "readonly";
+		}
+	}
+
+
+	#
+	# If we already have data, lets check how valid it is...
+	#
+
+	# We only do this if we have hash elements
+	if (ref($formData) eq "HASH") {
 		# Grab friendly name
-		my $friendlyName = $params->{'inputFriendlyName'};
+		my $friendlyName = $formData->{'FriendlyName'};
 
 		# Check POST data
 		my $username;
-		if (!defined($username = isUsername($params->{'inputUsername'}))) {
+		if (!defined($username = isUsername($formData->{'Username'}))) {
 			push(@errors,"Username is not valid");
 		}
 		my $ipAddress;
-		if (!defined($ipAddress = isIP($params->{'inputIP'}))) {
+		if (!defined($ipAddress = isIP($formData->{'IP'}))) {
 			push(@errors,"IP address is not valid");
 		}
-		my $trafficClass;
-		if (!defined($trafficClass = isTrafficClassValid($params->{'inputTrafficClass'}))) {
+		my $classID;
+		if (!defined($classID = isTrafficClassValid($formData->{'ClassID'}))) {
 			push(@errors,"Traffic class is not valid");
 		}
-		my $trafficLimitTx = isNumber($params->{'inputLimitTx'});
-		my $trafficLimitTxBurst = isNumber($params->{'inputLimitTxBurst'});
+		my $trafficLimitTx = isNumber($formData->{'TrafficLimitTx'});
+		my $trafficLimitTxBurst = isNumber($formData->{'TrafficLimitTxBurst'});
 		if (!defined($trafficLimitTx) && !defined($trafficLimitTxBurst)) {
 			push(@errors,"A valid download CIR and/or limit is required");
 		}
-		my $trafficLimitRx = isNumber($params->{'inputLimitRx'});
-		my $trafficLimitRxBurst = isNumber($params->{'inputLimitRxBurst'});
+		my $trafficLimitRx = isNumber($formData->{'TrafficLimitRx'});
+		my $trafficLimitRxBurst = isNumber($formData->{'TrafficLimitRxBurst'});
 		if (!defined($trafficLimitRx) && !defined($trafficLimitRxBurst)) {
 			push(@errors,"A valid upload CIR and/or limit is required");
 		}
 
 		my $expires = 0;
-		if (defined($params->{'inputExpires'}) && $params->{'inputExpires'} ne "") {
-			if (!defined($expires = isNumber($params->{'inputExpires'}))) {
+		if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
+			if (!defined($expires = isNumber($formData->{'Expires'}))) {
 				push(@errors,"Expires value is not valid");
 			# Check the modifier
 			} else {
 				# Check if its defined
-				if (defined($params->{'inputExpiresModifier'}) && $params->{'inputExpiresModifier'} ne "") {
+				if (defined($formData->{'inputExpiresModifier'}) && $formData->{'inputExpiresModifier'} ne "") {
 					# Minutes
-					if ($params->{'inputExpiresModifier'} eq "m") {
+					if ($formData->{'inputExpiresModifier'} eq "m") {
 						$expires *= 60;
 					# Hours
-					} elsif ($params->{'inputExpiresModifier'} eq "h") {
+					} elsif ($formData->{'inputExpiresModifier'} eq "h") {
 						$expires *= 3600;
 					# Days
-					} elsif ($params->{'inputExpiresModifier'} eq "d") {
+					} elsif ($formData->{'inputExpiresModifier'} eq "d") {
 						$expires *= 86400;
 					} else {
 						push(@errors,"Expires modifier is not valid");
 					}
 				}
-				# Set right time for expiry
-				$expires += time();
+				# Base the expiry off now, plus the expiry time
+				if ($expires > 0) {
+					$expires += time();
+				}
 			}
 		}
 		# Grab notes
-		my $notes = $params->{'inputNotes'};
+		my $notes = $formData->{'Notes'};
 
 		# If there are no errors we need to push this update
-		if (!@errors) {
+		if (!@errors && $request->method eq "POST") {
 			# Build limit
 			my $limit = {
 				'FriendlyName' => $friendlyName,
 				'Username' => $username,
 				'IP' => $ipAddress,
 				'GroupID' => 1,
-				'ClassID' => $trafficClass,
+				'ClassID' => $classID,
 				'TrafficLimitTx' => $trafficLimitTx,
 				'TrafficLimitTxBurst' => $trafficLimitTxBurst,
 				'TrafficLimitRx' => $trafficLimitRx,
 				'TrafficLimitRxBurst' => $trafficLimitRxBurst,
 				'Expires' => $expires,
 				'Notes' => $notes,
-				'Source' => "plugin.webserver.limits",
 			};
 
-			# Throw the change at the config manager
+			# Throw the change at the config manager after we add extra data we need
+			if ($formType eq "Add") {
+				$limit->{'Status'} = 'online';
+				$limit->{'Source'} = 'plugin.webserver.limits';
+			}
+
 			$kernel->post("configmanager" => "process_limit_change" => $limit);
 
-			$logger->log(LOG_INFO,'[WEBSERVER/LIMITS/ADD] User: %s, IP: %s, Group: %s, Class: %s, Limits: %s/%s, Burst: %s/%s',
+			$logger->log(LOG_INFO,'[WEBSERVER/LIMITS] Acount: %s, User: %s, IP: %s, Group: %s, Class: %s, Limits: %s/%s, Burst: %s/%s',
+					$formType,
 					prettyUndef($username),
 					prettyUndef($ipAddress),
 					undef,
-					prettyUndef($trafficClass),
+					prettyUndef($classID),
 					prettyUndef($trafficLimitTx),
 					prettyUndef($trafficLimitRx),
 					prettyUndef($trafficLimitTxBurst),
@@ -375,8 +432,8 @@ sub add
 	}
 
 	# Sanitize params if we need to
-	foreach my $item (keys %{$params}) {
-		$params->{$item} = defined($params->{$item}) ? encode_entities($params->{$item}) : "";	
+	foreach my $item (@formElements) {
+		$formData->{$item} = defined($formData->{$item}) ? encode_entities($formData->{$item}) : "";
 	}
 
 	# Build content
@@ -385,7 +442,7 @@ sub add
 	# Form header
 	$content .=<<EOF;
 <form role="form" method="post">
-	<legend>Add Manual Limit</legend>
+	<legend>$formType Manual Limit</legend>
 EOF
 
 	# Spit out errors if we have any
@@ -399,55 +456,69 @@ EOF
 	my $trafficClasses = getTrafficClasses();
 	my $trafficClassStr = "";
 	foreach my $classID (sort keys %{$trafficClasses}) {
-		$trafficClassStr .= '<option value="'.$classID.'">'.$trafficClasses->{$classID}.'</option>';
+		# Process selections nicely
+		my $selected = "";
+		if ($formData->{'ClassID'} ne "" && $formData->{'ClassID'} eq $classID) {
+			$selected = "selected";
+		}
+		# And build the options
+		$trafficClassStr .= '<option value="'.$classID.'" '.$selected.'>'.$trafficClasses->{$classID}.'</option>';
 	}
 
-	# Header
+	# Make expires look nicer
+	my $expiresStr = "";
+	if (defined($formData->{'Expires'}) && $formData->{'Expires'} > 0) {
+		$expiresStr = $formData->{'Expires'};
+	}
+
+	#
+	# Page content
+	#
 	$content .=<<EOF;
 	<div class="form-group">
-		<label for="inputFriendlyName" class="col-lg-2 control-label">Friendly Name</label>
+		<label for="FriendlyName" class="col-lg-2 control-label">Friendly Name</label>
 		<div class="row">
 			<div class="col-lg-4 input-group">
-				<input name="inputFriendlyName" type="text" placeholder="Opt. Friendly Name" class="form-control" value="$params->{'inputFriendlyName'}" />
+				<input name="FriendlyName" type="text" placeholder="Opt. Friendly Name" class="form-control" value="$formData->{'FriendlyName'}" />
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputUsername" class="col-lg-2 control-label">Username</label>
+		<label for="Username" class="col-lg-2 control-label">Username</label>
 		<div class="row">
 			<div class="col-lg-4 input-group">
-				<input name="inputUsername" type="text" placeholder="Username" class="form-control" value="$params->{'inputUsername'}" />
+				<input name="Username" type="text" placeholder="Username" class="form-control" value="$formData->{'Username'}" $formNoEdit/>
 				<span class="input-group-addon">*</span>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputIP" class="col-lg-2 control-label">IP Address</label>
+		<label for="IP" class="col-lg-2 control-label">IP Address</label>
 		<div class="row">
 			<div class="col-lg-4 input-group">
-				<input name="inputIP" type="text" placeholder="IP Address" class="form-control" value="$params->{'inputIP'}" />
+				<input name="IP" type="text" placeholder="IP Address" class="form-control" value="$formData->{'IP'}" $formNoEdit/>
 				<span class="input-group-addon">*</span>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputTafficClass" class="col-lg-2 control-label">Traffic Class</label>
+		<label for="ClassID" class="col-lg-2 control-label">Traffic Class</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<select name="inputTrafficClass" placeholder="Traffic Class" class="form-control" value="$params->{'inputTrafficClass'}">
+				<select name="ClassID" class="form-control">
 					$trafficClassStr
 				</select>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputExpires" class="col-lg-2 control-label">Expires</label>
+		<label for="Expires" class="col-lg-2 control-label">Expires</label>
 		<div class="row">
 			<div class="col-lg-2">
-				<input name="inputExpires" type="text" placeholder="Opt. Expires" class="form-control" value="$params->{'inputExpires'}" />
+				<input name="Expires" type="text" placeholder="Opt. Expires" class="form-control" value="$expiresStr" />
 			</div>
 			<div class="col-lg-2">
-				<select name="inputExpiresModifier" placeholder="Expires Modifier" class="form-control" value="$params->{'inputExpiresModifier'}">
+				<select name="inputExpires.modifier" class="form-control" value="$formData->{'inputExpires.modifier'}">
 					<option value="m">Mins</option>
 					<option value="h">Hours</option>
 					<option value="d">Days</option>
@@ -456,19 +527,19 @@ EOF
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputLimitTx" class="col-lg-2 control-label">Download CIR</label>
+		<label for="TrafficLimitTx" class="col-lg-2 control-label">Download CIR</label>
 		<div class="row">
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitTx" type="text" placeholder="Download CIR" class="form-control" value="$params->{'inputLimitTx'}" />
+					<input name="TrafficLimitTx" type="text" placeholder="Download CIR" class="form-control" value="$formData->{'TrafficLimitTx'}" />
 					<span class="input-group-addon">Kbps *<span>
 				</div>
 			</div>
 
-			<label for="inputLimitTxBurst" class="col-lg-1 control-label">Limit</label>
+			<label for="TrafficLimitTxBurst" class="col-lg-1 control-label">Limit</label>
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitTxBurst" type="text" placeholder="Download Limit" class="form-control" value="$params->{'inputLimitTxBurst'}" />
+					<input name="TrafficLimitTxBurst" type="text" placeholder="Download Limit" class="form-control" value="$formData->{'TrafficLimitTxBurst'}" />
 					<span class="input-group-addon">Kbps<span>
 				</div>
 			</div>
@@ -476,34 +547,34 @@ EOF
 	</div>
 
 	<div class="form-group">
-		<label for="inputLimitRx" class="col-lg-2 control-label">Upload CIR</label>
+		<label for="TrafficLimitRx" class="col-lg-2 control-label">Upload CIR</label>
 		<div class="row">
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitRx" type="text" placeholder="Upload CIR" class="form-control" value="$params->{'inputLimitRx'}" />
+					<input name="TrafficLimitRx" type="text" placeholder="Upload CIR" class="form-control" value="$formData->{'TrafficLimitRx'}" />
 					<span class="input-group-addon">Kbps *<span>
 				</div>
 			</div>
 
-			<label for="inputLimitRxBurst" class="col-lg-1 control-label">Limit</label>
+			<label for="TrafficLimitRxBurst" class="col-lg-1 control-label">Limit</label>
 			<div class="col-lg-3">
 				<div class="input-group">
-					<input name="inputLimitRxBurst" type="text" placeholder="Upload Limit" class="form-control" value="$params->{'inputLimitRxBurst'}" />
+					<input name="TrafficLimitRxBurst" type="text" placeholder="Upload Limit" class="form-control" value="$formData->{'TrafficLimitRxBurst'}" />
 					<span class="input-group-addon">Kbps<span>
 				</div>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<label for="inputNotes" class="col-lg-2 control-label">Notes</label>
+		<label for="Notes" class="col-lg-2 control-label">Notes</label>
 		<div class="row">
 			<div class="col-lg-4">
-				<textarea name="inputNotes" placeholder="Opt. Notes" rows="3" class="form-control"></textarea>
+				<textarea name="Notes" placeholder="Opt. Notes" rows="3" class="form-control"></textarea>
 			</div>
 		</div>
 	</div>
 	<div class="form-group">
-		<button type="submit" class="btn btn-primary">Add</button>
+		<button type="submit" class="btn btn-primary">$formType</button>
 		<button name="cancel" type="submit" class="btn">Cancel</button>
 	</div>
 </form>
@@ -513,5 +584,84 @@ EOF
 }
 
 
+# Remove action
+sub limit_remove
+{
+	my ($kernel,$globals,$client_session_id,$request) = @_;
+
+
+	# Content to return
+	my $content = "";
+
+
+	# Pull in GET
+	my $queryParams = parseURIQuery($request);
+	# We need a key first of all...
+	if (!defined($queryParams->{'lid'})) {
+		$content = <<EOF;
+			<div class="alert alert-danger text-center">
+				No limit ID in query string!
+			</div>
+EOF
+		goto END;
+	}
+
+	# Grab the limit
+	my $limit = getLimit($queryParams->{'lid'});
+
+	# Make the key safe for HTML
+	my $encodedLID = encode_entities($queryParams->{'lid'});
+
+	# Make sure the limit ID is valid... we would have a limit now if it was
+	if (!defined($limit)) {
+		$content = <<EOF;
+			<div class="alert alert-danger text-center">
+				Invalid limit ID "$encodedLID"!
+			</div>
+EOF
+		goto END;
+	}
+
+	# Make sure its a manual limit we're removing
+	if ($limit->{'Source'} ne "plugin.webserver.limits") {
+		$content = <<EOF;
+			<div class="alert alert-danger text-center">
+				Only manual limits can be removed!
+			</div>
+EOF
+		goto END;
+	}
+
+	# Pull in POST
+	my $postParams = parseFormContent($request->content);
+	# If this is a post, then its probably a confirmation
+	if (defined($postParams->{'confirm'})) {
+		# Check if its a success
+		if ($postParams->{'confirm'} eq "Yes") {
+			# Post the removal
+			$kernel->post("configmanager" => "process_limit_remove" => $limit);
+		}
+		return (HTTP_TEMPORARY_REDIRECT,'limits');
+	}
+
+
+	# Make the friendly name HTML safe
+	my $encodedUsername = encode_entities($limit->{'Username'});
+
+	# Build our confirmation dialog
+	$content .= <<EOF;
+		<div class="alert alert-danger">
+			Are you very sure you wish to remove limit for "$encodedUsername"?
+		</div>
+		<form role="form" method="post">
+			<input type="submit" class="btn btn-primary" name="confirm" value="Yes" />
+			<input type="submit" class="btn btn-default" name="confirm" value="No" />
+		</form>
+EOF
+	# And here is where we return
+END:
+	return (HTTP_OK,$content,{ 'menu' => $menu });
+}
+
 1;
 # vim: ts=4
diff --git a/opentrafficshaper/plugins/webserver/webserver.pm b/opentrafficshaper/plugins/webserver/webserver.pm
index 7f6ffe2..17de751 100644
--- a/opentrafficshaper/plugins/webserver/webserver.pm
+++ b/opentrafficshaper/plugins/webserver/webserver.pm
@@ -86,7 +86,9 @@ my $resources = {
 		},
 		'limits' => {
 			'default' => \&opentrafficshaper::plugins::webserver::pages::limits::default,
-			'add' => \&opentrafficshaper::plugins::webserver::pages::limits::add,
+			'limit-add' => \&opentrafficshaper::plugins::webserver::pages::limits::limit_addedit,
+			'limit-remove' => \&opentrafficshaper::plugins::webserver::pages::limits::limit_remove,
+			'limit-edit' => \&opentrafficshaper::plugins::webserver::pages::limits::limit_addedit,
 		},
 		'statistics' => {
 			'by-username' => \&opentrafficshaper::plugins::webserver::pages::statistics::byusername,
@@ -94,7 +96,9 @@ my $resources = {
 		},
 		'configmanager' => {
 			'default' => \&opentrafficshaper::plugins::webserver::pages::configmanager::default,
-			'add' => \&opentrafficshaper::plugins::webserver::pages::configmanager::add,
+			'override-add' => \&opentrafficshaper::plugins::webserver::pages::configmanager::override_addedit,
+			'override-remove' => \&opentrafficshaper::plugins::webserver::pages::configmanager::override_remove,
+			'override-edit' => \&opentrafficshaper::plugins::webserver::pages::configmanager::override_addedit,
 		},
 	},
 };
-- 
GitLab