From 5c6e37ccd918180a0f5bceba897732efde7b8e21 Mon Sep 17 00:00:00 2001
From: Nigel Kukard <nkukard@lbsd.net>
Date: Tue, 27 Aug 2013 10:16:52 +0000
Subject: [PATCH] Refactored code to use limits instead of users

---
 opentrafficshaper/plugins/configmanager.pm    | 317 ++++++++------
 .../plugins/statistics/statistics.pm          |  56 ++-
 opentrafficshaper/plugins/tc/tc.pm            | 114 ++---
 opentrafficshaper/plugins/tcstats/tcstats.pm  |  21 +-
 .../plugins/webserver/pages/limits.pm         | 400 ++++++++++++++++++
 .../plugins/webserver/pages/statistics.pm     | 200 +++++++++
 .../plugins/webserver/pages/users.pm          | 278 ------------
 .../websockets/statistics/statistics.pm       |   4 +-
 .../plugins/webserver/webserver.pm            | 163 +++----
 9 files changed, 1014 insertions(+), 539 deletions(-)
 create mode 100644 opentrafficshaper/plugins/webserver/pages/limits.pm
 create mode 100644 opentrafficshaper/plugins/webserver/pages/statistics.pm
 delete mode 100644 opentrafficshaper/plugins/webserver/pages/users.pm

diff --git a/opentrafficshaper/plugins/configmanager.pm b/opentrafficshaper/plugins/configmanager.pm
index b7889b6..281a128 100644
--- a/opentrafficshaper/plugins/configmanager.pm
+++ b/opentrafficshaper/plugins/configmanager.pm
@@ -37,12 +37,22 @@ our (@ISA,@EXPORT,@EXPORT_OK);
 @EXPORT = qw(
 );
 @EXPORT_OK = qw(
+		getLimits
+		setLimitAttribute
+		getLimitAttribute
+
+		getShaperState
+		setShaperState
+
+		getTrafficClasses
+
+		getPriorityName
 );
 
 use constant {
 	VERSION => '0.0.1',
 
-	# After how long does a user get removed if he's offline
+	# After how long does a limit get removed if its's deemed offline
 	TIMEOUT_EXPIRE_OFFLINE => 300,
 
 	# How often our config check ticks
@@ -85,10 +95,11 @@ my $default_pool_priority = 10;
 
 # Pending changes
 my $changeQueue = { };
-# UserID counter
-my $userIPMap = {};
-my $userIDMap = {};
-my $userIDCounter = 1;
+# Limits
+my $limits = { };
+my $limitIPMap = { };
+my $limitIDMap = { };
+my $limitIDCounter = 1;
 
 
 
@@ -218,9 +229,6 @@ sub session_tick {
 	my $kernel = $_[KERNEL];
 
 
-	# Suck in global
-	my $users = $globals->{'users'};
-
 	# Now
 	my $now = time();
 
@@ -229,186 +237,186 @@ sub session_tick {
 	# LOOP WITH CHANGES
 	#
 
-	foreach my $uid (keys %{$changeQueue}) {
-		# Changes for user
+	foreach my $lid (keys %{$changeQueue}) {
+		# Changes for limit
 		# Minimum required info is:
 		# - Username
 		# - IP
 		# - Status
 		# - LastUpdate
-		my $cuser = $changeQueue->{$uid};
+		my $climit = $changeQueue->{$lid};
 
 		#
-		# USER IN LIST
+		# LIMIT IN LIST
 		#
-		if (defined(my $guser = $users->{$uid})) {
+		if (defined(my $glimit = $limits->{$lid})) {
 
-			# This is a new user notification
-			if ($cuser->{'Status'} eq "new") {
-				$logger->log(LOG_INFO,"[CONFIGMANAGER] User '$cuser->{'Username'}' [$uid], user already live but new state provided?");
+			# This is a new limit notification
+			if ($climit->{'Status'} eq "new") {
+				$logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit already live but new state provided?");
 
 				# Get the changes we made and push them to the shaper
-				if (my $changes = processChanges($guser,$cuser)) {
+				if (my $changes = processChanges($glimit,$climit)) {
 					# Post to shaper
-					$kernel->post("shaper" => "change" => $uid => $changes);
+					$kernel->post("shaper" => "change" => $lid => $changes);
 				}
 
 				# Remove from change queue
-				delete($changeQueue->{$uid});
+				delete($changeQueue->{$lid});
 
 			# Online or "ping" status notification
-			} elsif ($cuser->{'Status'} eq "online") {
-				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] User '$cuser->{'Username'}' [$uid], user still online");
+			} elsif ($climit->{'Status'} eq "online") {
+				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit still online");
 
 				# Get the changes we made and push them to the shaper
-				if (my $changes = processChanges($guser,$cuser)) {
+				if (my $changes = processChanges($glimit,$climit)) {
 					# Post to shaper
-					$kernel->post("shaper" => "change" => $uid => $changes);
+					$kernel->post("shaper" => "change" => $lid => $changes);
 				}
 
 				# Remove from change queue
-				delete($changeQueue->{$uid});
+				delete($changeQueue->{$lid});
 
 			# Offline notification, this we going to treat specially
-			} elsif ($cuser->{'Status'} eq "offline") {
+			} elsif ($climit->{'Status'} eq "offline") {
 
 				# We first check if this update was received some time ago, and if it exceeds our expire time
-				# We don't want to immediately remove a user, only for him to come back on a few seconds later, the cost in exec()'s 
+				# We don't want to immediately remove a limit, only for him to come back on a few seconds later, the cost in exec()'s 
 				# would be pretty high
-				if ($now - $cuser->{'LastUpdate'} > TIMEOUT_EXPIRE_OFFLINE) {
+				if ($now - $climit->{'LastUpdate'} > TIMEOUT_EXPIRE_OFFLINE) {
 
 					# Remove entry if no longer live
-					if ($guser->{'_shaper.state'} == SHAPER_NOTLIVE) {
-						$logger->log(LOG_INFO,"[CONFIGMANAGER] User '$cuser->{'Username'}' [$uid] offline and removed from shaper");
+					if ($glimit->{'_shaper.state'} == SHAPER_NOTLIVE) {
+						$logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline and removed from shaper");
 
 						# Remove from system
-						delete($users->{$uid});
+						delete($limits->{$lid});
 						# Remove from change queue
-						delete($changeQueue->{$uid});
+						delete($changeQueue->{$lid});
 						# Set this UID as no longer using this IP
-						# NK: If we try remove it before the user is actually removed we could get a reconnection causing this value 
-						#     to be totally gone, which means we not tracking this user using this IP anymore, not easily solved!!
-						delete($userIPMap->{$guser->{'IP'}}->{$uid});
+						# NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value 
+						#     to be totally gone, which means we not tracking this limit using this IP anymore, not easily solved!!
+						delete($limitIPMap->{$glimit->{'IP'}}->{$lid});
 						# Check if we can delete the IP too
-						if (keys %{$userIPMap->{$guser->{'IP'}}} == 0) {
-							delete($userIPMap->{$guser->{'IP'}});
+						if (keys %{$limitIPMap->{$glimit->{'IP'}}} == 0) {
+							delete($limitIPMap->{$glimit->{'IP'}});
 						}
 
 						# Next record, we don't want to do any updates below
 						next;
 
 					# Push to shaper
-					} elsif ($guser->{'_shaper.state'} == SHAPER_LIVE) {
-						$logger->log(LOG_DEBUG,"[CONFIGMANAGER] User '$cuser->{'Username'}' [$uid] offline, queue remove from shaper");
+					} elsif ($glimit->{'_shaper.state'} == SHAPER_LIVE) {
+						$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline, queue remove from shaper");
 
 						# Post removal to shaper
-						$kernel->post("shaper" => "remove" => $uid);
+						$kernel->post("shaper" => "remove" => $lid);
 
 					} else {
-						$logger->log(LOG_DEBUG,"[CONFIGMANAGER] User '$cuser->{'Username'}' [$uid], user in list, but offline now and".
+						$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit in list, but offline now and".
 								" expired, still live, waiting for shaper");
 					}
 				}
 			}
 
-			# Update the user data
-			$guser->{'Status'} = $cuser->{'Status'};
-			$guser->{'LastUpdate'} = $cuser->{'LastUpdate'};
+			# Update the limit data
+			$glimit->{'Status'} = $climit->{'Status'};
+			$glimit->{'LastUpdate'} = $climit->{'LastUpdate'};
 			# This item is optional
-			$guser->{'Expires'} = $cuser->{'Expires'} if (defined($cuser->{'Expires'}));
+			$glimit->{'Expires'} = $climit->{'Expires'} if (defined($climit->{'Expires'}));
 
 		#
-		# USER NOT IN LIST
+		# LIMIT NOT IN LIST
 		#
 		} else {
-			# We take new and online notifications the same way here if the user is not in our global userlist already
-		   if (($cuser->{'Status'} eq "new" || $cuser->{'Status'} eq "online")) {
-				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Processing new user '$cuser->{'Username'}' [$uid]");
+			# We take new and online notifications the same way here if the limit is not in our global limit list already
+		   if (($climit->{'Status'} eq "new" || $climit->{'Status'} eq "online")) {
+				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Processing new limit '$climit->{'Username'}' [$lid]");
 
 				# We first going to look for IP conflicts...
-				my @ipUsers = keys %{$userIPMap->{$cuser->{'IP'}}};
+				my @ipLimits = keys %{$limitIPMap->{$climit->{'IP'}}};
 				if (
 					# If there is already an entry and its not us ...
-					( @ipUsers == 1 && !defined($userIPMap->{$cuser->{'IP'}}->{$uid}) )
+					( @ipLimits == 1 && !defined($limitIPMap->{$climit->{'IP'}}->{$lid}) )
 					# Or if there is more than 1 entry...
-					|| @ipUsers > 1 
+					|| @ipLimits > 1 
 				) {
 					# We not going to post this to the shaper, but we are going to override the status
-					$cuser->{'Status'} = 'conflict';
-					$cuser->{'_shaper.state'} = SHAPER_NOTLIVE;
+					$climit->{'Status'} = 'conflict';
+					$climit->{'_shaper.state'} = SHAPER_NOTLIVE;
 					# Give a bit of info
 					my @conflictUsernames;
-					foreach my $uid (@ipUsers) {
-						push(@conflictUsernames,$users->{$uid}->{'Username'});
+					foreach my $lid (@ipLimits) {
+						push(@conflictUsernames,$limits->{$lid}->{'Username'});
 					}
 					# Output log line
-					$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process user '".$cuser->{'Username'}."' IP '$cuser->{'IP'}' conflicts with users '".
+					$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit '".$climit->{'Username'}."' IP '$climit->{'IP'}' conflicts with users '".
 							join(',',@conflictUsernames)."'.");
 
-					# We cannot trust shaping when there is more than 1 user on the IP, so we going to remove all users with this
+					# We cannot trust shaping when there is more than 1 limit on the IP, so we going to remove all limits with this
 					# IP from the shaper below...
-					foreach my $uid2 (@ipUsers) {
-						# Check if the user has been setup already (all but the user we busy with, as its setup below)
-						if (defined($userIPMap->{$cuser->{'IP'}}->{$uid2})) {
-							my $guser2 = $users->{$uid2};
-
-							# If the user is active or pending on the shaper, remove it
-							if ($guser2->{'_shaper.state'} == SHAPER_LIVE || $guser2->{'_shaper.state'} == SHAPER_PENDING) {
-								$logger->log(LOG_WARN,"[CONFIGMANAGER] Removing conflicted user '".$guser2->{'Username'}."' [$uid2] from shaper'");
+					foreach my $lid2 (@ipLimits) {
+						# Check if the limit has been setup already (all but the limit we busy with, as its setup below)
+						if (defined($limitIPMap->{$climit->{'IP'}}->{$lid2})) {
+							my $glimit2 = $limits->{$lid2};
+
+							# If the limit is active or pending on the shaper, remove it
+							if ($glimit2->{'_shaper.state'} == SHAPER_LIVE || $glimit2->{'_shaper.state'} == SHAPER_PENDING) {
+								$logger->log(LOG_WARN,"[CONFIGMANAGER] Removing conflicted limit '".$glimit2->{'Username'}."' [$lid2] from shaper'");
 								# Post removal to shaper
-								$kernel->post("shaper" => "remove" => $uid2);
-								# Update that we're offline directly to global user table
-								$guser2->{'Status'} = 'conflict';
+								$kernel->post("shaper" => "remove" => $lid2);
+								# Update that we're offline directly to global limit table
+								$glimit2->{'Status'} = 'conflict';
 							}
 						}
 					}
 
-				# All looks good, no conflicts, we're set to add this user!
+				# All looks good, no conflicts, we're set to add this limit!
 				} else {
-					# Post to the user to the shaper
-					$cuser->{'_shaper.state'} = SHAPER_PENDING;
-					$kernel->post("shaper" => "add" => $uid);
+					# Post to the limit to the shaper
+					$climit->{'_shaper.state'} = SHAPER_PENDING;
+					$kernel->post("shaper" => "add" => $lid);
 
 				}
 
 				# Set this UID as using this IP
-				$userIPMap->{$cuser->{'IP'}}->{$uid} = 1;
+				$limitIPMap->{$climit->{'IP'}}->{$lid} = 1;
 
 				# This is now live
-				$users->{$uid} = $cuser;
+				$limits->{$lid} = $climit;
 
-			# User is not in our list and this is an unknown state we're trasitioning to
+			# Limit is not in our list and this is an unknown state we're trasitioning to
 			} else {
-				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Ignoring user '$cuser->{'Username'}' [$uid] state '$cuser->{'Status'}', not in our".
+				$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Ignoring limit '$climit->{'Username'}' [$lid] state '$climit->{'Status'}', not in our".
 						" global list");
 			}
 	
 			# Remove from change queue
-			delete($changeQueue->{$uid});
+			delete($changeQueue->{$lid});
 		}
 
 	}
 
 
 	#
-	# CHECK OUT CONNECTED USERS
+	# CHECK OUT CONNECTED LIMITS
 	#
-	foreach my $uid (keys %{$users}) {
-		# Global user
-		my $guser = $users->{$uid};
-
-		# Check for expired users
-		if ($guser->{'Expires'} != 0 && $guser->{'Expires'} < $now) {
-			$logger->log(LOG_INFO,"[CONFIGMANAGER] User '$guser->{'Username'}' has expired, marking offline");
-			# Looks like this user has expired?
-			my $cuser = {
-				'Username' => $guser->{'Username'},
-				'IP' => $guser->{'IP'},
+	foreach my $lid (keys %{$limits}) {
+		# Global limit
+		my $glimit = $limits->{$lid};
+
+		# Check for expired limits
+		if ($glimit->{'Expires'} != 0 && $glimit->{'Expires'} < $now) {
+			$logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$glimit->{'Username'}' has expired, marking offline");
+			# Looks like this limit has expired?
+			my $climit = {
+				'Username' => $glimit->{'Username'},
+				'IP' => $glimit->{'IP'},
 				'Status' => 'offline',
-				'LastUpdate' => $guser->{'LastUpdate'},
+				'LastUpdate' => $glimit->{'LastUpdate'},
 			};
 			# Add to change queue
-			$changeQueue->{$uid} = $cuser;
+			$changeQueue->{$lid} = $climit;
 		}
 	}
 
@@ -444,73 +452,73 @@ sub session_tick {
 #  - online
 #  - unknown
 # Source 
-#  - This is the source of the user, typically  plugin.ModuleName
+#  - This is the source of the limit, typically  plugin.ModuleName
 sub process_change {
-	my ($kernel, $user) = @_[KERNEL, ARG0];
+	my ($kernel, $limit) = @_[KERNEL, ARG0];
 
 
 
-	# Create a unique user identifier
-	my $userUniq = $user->{'Username'} . "/" . $user->{'IP'};
+	# Create a unique limit identifier
+	my $limitUniq = $limit->{'Username'} . "/" . $limit->{'IP'};
 
 	# If we've not seen it
-	my $uid;
-	if (!defined($uid = $userIDMap->{$userUniq})) {
-		# Give it the next userID in the list
-		$userIDMap->{$userUniq} = $uid = ++$userIDCounter;
+	my $lid;
+	if (!defined($lid = $limitIDMap->{$limitUniq})) {
+		# Give it the next limitID in the list
+		$limitIDMap->{$limitUniq} = $lid = ++$limitIDCounter;
 	}
 
 	# We start off blank so we only pull in whats supported
-	my $userChange;
-	if (!($userChange->{'Username'} = $user->{'Username'})) {
-		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change as username is invalid.");
+	my $limitChange;
+	if (!($limitChange->{'Username'} = $limit->{'Username'})) {
+		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change as username is invalid.");
 	}
-	$userChange->{'Username'} = $user->{'Username'};
-	$userChange->{'IP'} = $user->{'IP'};
+	$limitChange->{'Username'} = $limit->{'Username'};
+	$limitChange->{'IP'} = $limit->{'IP'};
 	# Check group is OK
-	if (!($userChange->{'GroupID'} = checkGroupID($user->{'GroupID'}))) {
-		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change for '".$user->{'Username'}."' as the GroupID is invalid.");
+	if (!($limitChange->{'GroupID'} = checkGroupID($limit->{'GroupID'}))) {
+		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the GroupID is invalid.");
 	}
 	# Check class is OK
-	if (!($userChange->{'ClassID'} = checkClassID($user->{'ClassID'}))) {
-		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change for '".$user->{'Username'}."' as the ClassID is invalid.");
+	if (!($limitChange->{'ClassID'} = checkClassID($limit->{'ClassID'}))) {
+		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the ClassID is invalid.");
 	}
-	$userChange->{'TrafficLimitTx'} = $user->{'TrafficLimitTx'};
-	$userChange->{'TrafficLimitRx'} = $user->{'TrafficLimitRx'};
+	$limitChange->{'TrafficLimitTx'} = $limit->{'TrafficLimitTx'};
+	$limitChange->{'TrafficLimitRx'} = $limit->{'TrafficLimitRx'};
 	# Take base limits if we don't have any burst values set
-	$userChange->{'TrafficLimitTxBurst'} = $user->{'TrafficLimitTxBurst'};
-	$userChange->{'TrafficLimitRxBurst'} = $user->{'TrafficLimitRxBurst'};
+	$limitChange->{'TrafficLimitTxBurst'} = $limit->{'TrafficLimitTxBurst'};
+	$limitChange->{'TrafficLimitRxBurst'} = $limit->{'TrafficLimitRxBurst'};
 
 	# If we don't have burst limits, set them to the traffic limit, and reset the limit to 25%
-	if (!defined($userChange->{'TrafficLimitTxBurst'})) {
-		$userChange->{'TrafficLimitTxBurst'} = $userChange->{'TrafficLimitTx'};
-		$userChange->{'TrafficLimitTx'} = int($userChange->{'TrafficLimitTxBurst'}/4);
+	if (!defined($limitChange->{'TrafficLimitTxBurst'})) {
+		$limitChange->{'TrafficLimitTxBurst'} = $limitChange->{'TrafficLimitTx'};
+		$limitChange->{'TrafficLimitTx'} = int($limitChange->{'TrafficLimitTxBurst'}/4);
 	}
-	if (!defined($userChange->{'TrafficLimitRxBurst'})) {
-		$userChange->{'TrafficLimitRxBurst'} = $userChange->{'TrafficLimitRx'};
-		$userChange->{'TrafficLimitRx'} = int($userChange->{'TrafficLimitRxBurst'}/4);
+	if (!defined($limitChange->{'TrafficLimitRxBurst'})) {
+		$limitChange->{'TrafficLimitRxBurst'} = $limitChange->{'TrafficLimitRx'};
+		$limitChange->{'TrafficLimitRx'} = int($limitChange->{'TrafficLimitRxBurst'}/4);
 	}
 
 
 	# Optional priority, we default to 5
-	$userChange->{'TrafficPriority'} = defined($user->{'TrafficPriority'}) ? $user->{'TrafficPriority'} : 5;
+	$limitChange->{'TrafficPriority'} = defined($limit->{'TrafficPriority'}) ? $limit->{'TrafficPriority'} : 5;
 
 	# Set when this entry expires
-	$userChange->{'Expires'} = defined($user->{'Expires'}) ? $user->{'Expires'} : 0;
+	$limitChange->{'Expires'} = defined($limit->{'Expires'}) ? $limit->{'Expires'} : 0;
 
 	# Check status is OK
-	if (!($userChange->{'Status'} = checkStatus($user->{'Status'}))) {
-		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change for '".$user->{'Username'}."' as the Status is invalid.");
+	if (!($limitChange->{'Status'} = checkStatus($limit->{'Status'}))) {
+		$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process user change for '".$limit->{'Username'}."' as the Status is invalid.");
 	}
 
-	$userChange->{'Source'} = $user->{'Source'};
+	$limitChange->{'Source'} = $limit->{'Source'};
 
 	# Set the user ID before we post to the change queue
-	$userChange->{'ID'} = $uid;
-	$userChange->{'LastUpdate'} = time();
+	$limitChange->{'ID'} = $lid;
+	$limitChange->{'LastUpdate'} = time();
 
 	# Push change to change queue
-	$changeQueue->{$uid} = $userChange;
+	$changeQueue->{$lid} = $limitChange;
 }
 
 
@@ -558,6 +566,63 @@ sub checkStatus
 	return undef;
 }
 
+# Function to return list of limits
+sub getLimits
+{
+	return $limits;
+}
+
+# Function to set a limit attribute
+sub setLimitAttribute
+{
+	my ($lid,$attr,$value) = @_;
+
+	$limits->{$lid}->{'attributes'}->{$attr} = $value;
+}
+
+# Function to get a limit attribute
+sub getLimitAttribute
+{
+	my ($lid,$attr) = @_;
+
+
+	# Check if attribute exists first
+	if (defined($limits->{$lid}) && defined($limits->{$lid}->{'attributes'}) && defined($limits->{$lid}->{'attributes'}->{$attr})) {
+		return $limits->{$lid}->{'attributes'}->{$attr};
+	} else {
+		return undef;
+	}
+}
+
+# Function to set shaper state on a limit
+sub setShaperState
+{
+	my ($lid,$state) = @_;
+
+	$limits->{$lid}->{'_shaper.state'} = $state;
+}
+
+# Function to get shaper state on a limit
+sub getShaperState
+{
+	my $lid = shift;
+
+	return $limits->{$lid}->{'_shaper.state'};
+}
+
+# Function to get traffic classes
+sub getTrafficClasses
+{
+	return $classes;	
+}
+
+# Function to get priority name
+sub getPriorityName
+{
+	my $prio = shift;
+
+	return $classes->{$prio};
+}
 
 
 # Handle SIGHUP
diff --git a/opentrafficshaper/plugins/statistics/statistics.pm b/opentrafficshaper/plugins/statistics/statistics.pm
index 731242a..7ee3b88 100644
--- a/opentrafficshaper/plugins/statistics/statistics.pm
+++ b/opentrafficshaper/plugins/statistics/statistics.pm
@@ -28,6 +28,7 @@ use opentrafficshaper::constants;
 use opentrafficshaper::logger;
 use opentrafficshaper::utils;
 
+use opentrafficshaper::plugins::configmanager qw( getLimits );
 
 
 # Exporter stuff
@@ -37,6 +38,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
 @EXPORT = qw(
 );
 @EXPORT_OK = qw(
+		getLastStats
 );
 
 use constant {
@@ -138,7 +140,7 @@ sub session_init {
 	$logger->log(LOG_DEBUG,"[STATISTICS] Initialized");
 }
 
-# Update users Statistics
+# Update limit Statistics
 # $uid has some special use cases:
 #	main:$iface:all	- Interface total stats
 #	main:$iface:classes	- Interface classified traffic
@@ -147,18 +149,21 @@ sub do_update {
 	my ($kernel, $item, $stats) = @_[KERNEL, ARG0, ARG1];
 
 
-	# Save entry
-	$statsCache->{$item}->{$stats->{'timestamp'}} = $stats;
-
 	# Buffer size
 	$logger->log(LOG_INFO,"[STATISTICS] Statistics update for '%s', buffered '%s' items",$item,scalar keys %{$statsCache->{$item}});
 
 	if ($item =~ /^main/) {
 	} else {
 		# Pull in global
-		my $users = $globals->{'users'};
-		my $user = $users->{$item};
-
+		my $limits = getLimits();
+		my $limit = $limits->{$item};
+		my $username = $limit->{'Username'};
+		
+		# Save entry
+		$statsCache->{$username}->{$stats->{'timestamp'}}->{$stats->{'direction'}} = $stats;
+
+use Data::Dumper;		print STDERR "Limit: ".Dumper($limit);
+use Data::Dumper;		print STDERR "Stats: ".Dumper($stats);
 	}
 
 
@@ -191,7 +196,8 @@ print STDERR "Pass4: $event\n";
 
 
 # Handle subscriptions to updates
-sub do_subscribe {
+sub do_subscribe 
+{
 	my ($kernel, $handler, $handlerEvent, $item) = @_[KERNEL, ARG0, ARG1, ARG2];
 
 
@@ -202,7 +208,8 @@ sub do_subscribe {
 
 
 # Handle unsubscribes
-sub do_unsubscribe {
+sub do_unsubscribe
+{
 	my ($kernel, $handler, $handlerEvent, $item) = @_[KERNEL, ARG0, ARG1, ARG2];
 
 
@@ -211,11 +218,42 @@ sub do_unsubscribe {
 	delete($subscribers->{$item}->{$handler}->{$handlerEvent});
 }
 
+# Return user last stats
+sub getLastStats
+{
+	my $username = shift;
+
+	my $statistics;
+
+	# Do we have stats for this user in the cache?
+	if (defined($statsCache->{$username})) {
+		# Grab last entry
+		my $lastTimestamp = (sort keys %{$statsCache->{$username}})[-1];
+		# We should ALWAYS have one, unless the server just booted
+		if (defined($lastTimestamp)) {
+			# Loop with both directions
+			foreach my $direction ('tx','rx') {
+				# Get a easier to use handle on the stats
+				if (my $stats = $statsCache->{$username}->{$lastTimestamp}->{$direction}) {
+					# Setup the statistics hash
+					$statistics->{$direction} = {
+						'current_rate' => $stats->{'current_rate'},
+						'current_pps' => $stats->{'current_pps'},
+					};
+				}
+			}
+		}
+	}
+
+	return $statistics;
+}
+
 
 sub handle_SIGHUP
 {
 	$logger->log(LOG_WARN,"[STATISTICS] Got SIGHUP, ignoring for now");
 }
 
+
 1;
 # vim: ts=4
diff --git a/opentrafficshaper/plugins/tc/tc.pm b/opentrafficshaper/plugins/tc/tc.pm
index a4b56fc..16937bf 100644
--- a/opentrafficshaper/plugins/tc/tc.pm
+++ b/opentrafficshaper/plugins/tc/tc.pm
@@ -28,6 +28,10 @@ use opentrafficshaper::constants;
 use opentrafficshaper::logger;
 use opentrafficshaper::utils;
 
+use opentrafficshaper::plugins::configmanager qw( 
+		getLimits getLimitAttribute setLimitAttribute
+		getShaperState setShaperState
+);
 
 
 # Exporter stuff
@@ -198,17 +202,17 @@ sub session_init {
 
 # Add event for tc
 sub do_add {
-	my ($kernel,$heap,$uid) = @_[KERNEL, HEAP, ARG0];
+	my ($kernel,$heap,$lid) = @_[KERNEL, HEAP, ARG0];
 
 
 	# Pull in global
-	my $users = $globals->{'users'};
-	my $user = $users->{$uid};
+	my $limits = getLimits();
+	my $limit = $limits->{$lid};
 
-	$logger->log(LOG_DEBUG," Add '$user->{'Username'}' [$uid]\n");
+	$logger->log(LOG_DEBUG,"[TC] Add '$limit->{'Username'}' [$lid]\n");
 
 
-	my @components = split(/\./,$user->{'IP'});
+	my @components = split(/\./,$limit->{'IP'});
 
 	# Filter level 2-4
 	my $ip1 = $components[0];
@@ -219,7 +223,7 @@ sub do_add {
 	# Check if we have a entry for the /8, if not we must create our 2nd level hash table and link it
 	if (!defined($tcFilterMappings->{$ip1})) {
 		# Setup IP1's hash table
-		my $filterID  = getTcFilter($uid);
+		my $filterID  = getTcFilter($lid);
 		$tcFilterMappings->{$ip1}->{'id'} = $filterID;
 
 
@@ -283,7 +287,7 @@ sub do_add {
 
 	# Check if we have our /16 hash entry, if not we must create the 3rd level hash table
 	if (!defined($tcFilterMappings->{$ip1}->{$ip2})) {
-		my $filterID  = getTcFilter($uid);
+		my $filterID  = getTcFilter($lid);
 		# Set 2nd level hash table ID
 		$tcFilterMappings->{$ip1}->{$ip2}->{'id'} = $filterID;
 		# Grab some hash table ID's we need
@@ -350,7 +354,7 @@ sub do_add {
 
 	# Check if we have our /24 hash entry, if not we must create the 4th level hash table
 	if (!defined($tcFilterMappings->{$ip1}->{$ip2}->{$ip3})) {
-		my $filterID  = getTcFilter($uid);
+		my $filterID  = getTcFilter($lid);
 		# Set 3rd level hash table ID
 		$tcFilterMappings->{$ip1}->{$ip2}->{$ip3}->{'id'} = $filterID;
 		# Grab some hash table ID's we need
@@ -419,18 +423,18 @@ sub do_add {
 
 
 	# Only if we have limits setup process them
-	if (defined($user->{'TrafficLimitTx'}) && defined($user->{'TrafficLimitRx'})) {
-		# Build users tc class ID
-		my $classID  = getTcClass($uid);
+	if (defined($limit->{'TrafficLimitTx'}) && defined($limit->{'TrafficLimitRx'})) {
+		# Build limit tc class ID
+		my $classID  = getTcClass($lid);
 		# Grab some hash table ID's we need
 		my $ip3HtHex = $tcFilterMappings->{$ip1}->{$ip2}->{$ip3}->{'id'};
 		my $ip4Hex = toHex($ip4);
 		# Generate our filter handle
 		my $filterHandle = "${ip3HtHex}:${ip4Hex}:1";
 
-		# Save user tc class ID
-		$user->{'tc.class'} = $classID;
-		$user->{'tc.filter'} = "${ip3HtHex}:${ip4Hex}:1";
+		# Save limit tc class ID
+		setLimitAttribute($lid,'tc.class',$classID);
+		setLimitAttribute($lid,'tc.filter',"${ip3HtHex}:${ip4Hex}:1");
 
 		#
 		# SETUP MAIN TRAFFIC LIMITS
@@ -443,9 +447,9 @@ sub do_add {
 					'parent','1:2',
 					'classid',"1:$classID",
 					'htb',
-						'rate', $user->{'TrafficLimitTx'} . "kbit",
-						'ceil', $user->{'TrafficLimitTxBurst'} . "kbit",
-						'prio',$user->{'TrafficPriority'},
+						'rate', $limit->{'TrafficLimitTx'} . "kbit",
+						'ceil', $limit->{'TrafficLimitTxBurst'} . "kbit",
+						'prio', $limit->{'TrafficPriority'},
 		]);
 		$kernel->post("_tc" => "queue" => [
 				'/sbin/tc','class','add',
@@ -453,9 +457,9 @@ sub do_add {
 					'parent','1:2',
 					'classid',"1:$classID",
 					'htb',
-						'rate', $user->{'TrafficLimitRx'} . "kbit",
-						'ceil', $user->{'TrafficLimitRxBurst'} . "kbit",
-						'prio',$user->{'TrafficPriority'},
+						'rate', $limit->{'TrafficLimitRx'} . "kbit",
+						'ceil', $limit->{'TrafficLimitRxBurst'} . "kbit",
+						'prio', $limit->{'TrafficPriority'},
 		]);
 
 		#
@@ -472,7 +476,7 @@ sub do_add {
 					'protocol',$config->{'ip_protocol'},
 					'u32',
 						'ht',"${ip3HtHex}:${ip4Hex}:",
-							'match','ip','dst',$user->{'IP'},
+							'match','ip','dst',$limit->{'IP'},
 								'at',16+$config->{'iphdr_offset'},
 					'flowid',"1:$classID",
 		]);
@@ -485,35 +489,35 @@ sub do_add {
 					'protocol',$config->{'ip_protocol'},
 					'u32',
 						'ht',"${ip3HtHex}:${ip4Hex}:",
-							'match','ip','src',$user->{'IP'},
+							'match','ip','src',$limit->{'IP'},
 								'at',12+$config->{'iphdr_offset'},
 					'flowid',"1:$classID",
 		]);
 
-		tc_addtask_optimize($kernel,$config->{'txiface'},$classID,$user->{'TrafficLimitTx'});
-		tc_addtask_optimize($kernel,$config->{'rxiface'},$classID,$user->{'TrafficLimitRx'});
+		tc_addtask_optimize($kernel,$config->{'txiface'},$classID,$limit->{'TrafficLimitTx'});
+		tc_addtask_optimize($kernel,$config->{'rxiface'},$classID,$limit->{'TrafficLimitRx'});
 	}
 
 	# Mark as live
-	$user->{'_shaper.state'} = SHAPER_LIVE;
+	setShaperState($lid,SHAPER_LIVE);
 }
 
 # Change event for tc
 sub do_change {
-	my ($kernel, $uid, $changes) = @_[KERNEL, ARG0];
+	my ($kernel, $lid, $changes) = @_[KERNEL, ARG0];
 
 
 	# Pull in global
-	my $users = $globals->{'users'};
-	my $user = $users->{$uid};
+	my $limits = getLimits();
+	my $limit = $limits->{$lid};
 
-	$logger->log(LOG_DEBUG,"Processing changes for '$user->{'Username'}' [$uid]\n");
+	$logger->log(LOG_DEBUG,"Processing changes for '$limit->{'Username'}' [$lid]\n");
 
 	# We going to pull in the defaults
-	my $trafficLimitTx = $user->{'TrafficLimitTx'};
-	my $trafficLimitRx = $user->{'TrafficLimitRx'};
-	my $trafficLimitTxBurst = $user->{'TrafficLimitTxBurst'};
-	my $trafficLimitRxBurst = $user->{'TrafficLimitRxBurst'};
+	my $trafficLimitTx = $limit->{'TrafficLimitTx'};
+	my $trafficLimitRx = $limit->{'TrafficLimitRx'};
+	my $trafficLimitTxBurst = $limit->{'TrafficLimitTxBurst'};
+	my $trafficLimitRxBurst = $limit->{'TrafficLimitRxBurst'};
 	# Lets see if we can override them...
 	if (defined($changes->{'TrafficLimitTx'})) {
 		$trafficLimitTx = $changes->{'TrafficLimitTx'};
@@ -528,42 +532,45 @@ sub do_change {
 		$trafficLimitRxBurst = $changes->{'TrafficLimitRxBurst'};
 	}
 
+	my $classID = getLimitAttribute($lid,'tc.class');
+
+
 	$kernel->post("_tc" => "queue" => [
 			'/sbin/tc','class','change',
 				'dev',$config->{'txiface'},
 				'parent','1:2',
-				'classid',"1:$user->{'tc.class'}",
+				'classid',"1:$classID",
 				'htb',
 					'rate', $trafficLimitTx . "kbit",
 					'ceil', $trafficLimitTxBurst . "kbit",
-					'prio',$user->{'TrafficPriority'},
+					'prio', $limit->{'TrafficPriority'},
 	]);
 	$kernel->post("_tc" => "queue" => [
 			'/sbin/tc','class','change',
 				'dev',$config->{'rxiface'},
 				'parent','1:2',
-				'classid',"1:$user->{'tc.class'}",
+				'classid',"1:$classID",
 				'htb',
 					'rate', $trafficLimitRx . "kbit",
 					'ceil', $trafficLimitRxBurst . "kbit",
-					'prio',$user->{'TrafficPriority'},
+					'prio', $limit->{'TrafficPriority'},
 	]);
 }
 
 # Remove event for tc
 sub do_remove {
-	my ($kernel, $uid) = @_[KERNEL, ARG0];
+	my ($kernel, $lid) = @_[KERNEL, ARG0];
 
 
 	# Pull in global
-	my $users = $globals->{'users'};
-	my $user = $users->{$uid};
+	my $limits = getLimits();
+	my $limit = $limits->{$lid};
 
-	$logger->log(LOG_DEBUG," Remove '$user->{'Username'}' [$uid]\n");
+	$logger->log(LOG_DEBUG," Remove '$limit->{'Username'}' [$lid]\n");
 
 	# Grab ClassID
-	my $classID = $user->{'tc.class'};
-	my $filterHandle = $user->{'tc.filter'};
+	my $classID = getLimitAttribute($lid,'tc.class');
+	my $filterHandle = getLimitAttribute($lid,'tc.filter');
 
 	# Clear up the filter
 	$kernel->post("_tc" => "queue" => [
@@ -602,14 +609,14 @@ sub do_remove {
 	disposeTcClass($classID);
 
 	# Mark as not live
-	$user->{'_shaper.state'} = SHAPER_NOTLIVE;
+	setShaperState($lid,SHAPER_NOTLIVE);
 }
 
 
 # Function to get next available TC filter 
 sub getTcFilter
 {
-	my $uid = shift;
+	my $lid = shift;
 
 
 	my $id = pop(@{$tcFilters->{'free'}});
@@ -625,7 +632,7 @@ sub getTcFilter
 		$id = toHex($id);
 	}
 
-	$tcFilters->{'track'}->{$id} = $uid;
+	$tcFilters->{'track'}->{$id} = $lid;
 
 	return $id;
 }
@@ -644,7 +651,7 @@ sub disposeTcFilter
 # Function to get next available TC class 
 sub getTcClass
 {
-	my $uid = shift;
+	my $lid = shift;
 
 
 	my $id = pop(@{$tcClasses->{'free'}});
@@ -657,7 +664,7 @@ sub getTcClass
 		$id = toHex($id);
 	}
 
-	$tcClasses->{'track'}->{$id} = $uid;
+	$tcClasses->{'track'}->{$id} = $lid;
 
 	return $id;
 }
@@ -678,6 +685,17 @@ sub getUIDFromTcClass
 
 	return $tcClasses->{'track'}->{$id};
 }
+# Get TX iface
+sub getConfigTxIface
+{
+	return $config->{'txiface'};
+}
+# Get RX iface
+sub getConfigRxIface
+{
+	return $config->{'rxiface'};
+}
+
 
 # Function to initialize an interface
 sub _tc_init_iface
diff --git a/opentrafficshaper/plugins/tcstats/tcstats.pm b/opentrafficshaper/plugins/tcstats/tcstats.pm
index 551064f..f9d01fe 100644
--- a/opentrafficshaper/plugins/tcstats/tcstats.pm
+++ b/opentrafficshaper/plugins/tcstats/tcstats.pm
@@ -128,12 +128,26 @@ sub session_tick {
 
 	# Suck in global
 	my $users = $globals->{'users'};
+	my $tcConfig = $opentrafficshaper::plugins::tc::config;
 
 	# Now
 	my $now = time();
 
 	my $iface = "eth1";
 
+	# Work out traffic direction
+	my $direction;
+	if ($iface eq opentrafficshaper::plugins::tc::getConfigTxIface()) {
+		$direction = 'tx';
+	} elsif ($iface eq opentrafficshaper::plugins::tc::getConfigRxIface()) {
+		$direction = 'rx';
+	} else {
+		# Reset tick
+		$kernel->delay(tick => TICK_PERIOD);
+		$logger->log(LOG_ERR,"[TCSTATS] Unknown interface '$iface'");
+		return;
+	}
+
 	# TC commands to run
 	my $cmd = [ '/sbin/tc', '-s', 'class', 'show', 'dev', $iface ];
 
@@ -159,16 +173,13 @@ sub session_tick {
 	$heap->{task_data}->{$task->ID} = {
 		'timestamp' => $now,
 		'iface' => $iface,
+		'direction' => $direction,
 		'stats' => { }
 	};
 
 	# Build commandline string
 	my $cmdStr = join(' ',@{$cmd});
 	$logger->log(LOG_DEBUG,"[TCSTATS] TASK/".$task->ID.": Starting '$cmdStr' as ".$task->ID." with PID ".$task->PID);
-
-
-	# Reset tick
-#	$kernel->delay(tick => TICK_PERIOD);
 };
 
 
@@ -179,6 +190,7 @@ sub task_child_stdout
     my $child = $heap->{task_by_wid}->{$task_id};
     my $stats = $heap->{task_data}->{$task_id}->{'stats'};
     my $iface = $heap->{task_data}->{$task_id}->{'iface'};
+    my $direction = $heap->{task_data}->{$task_id}->{'direction'};
     my $timestamp = $heap->{task_data}->{$task_id}->{'timestamp'};
 
 
@@ -275,6 +287,7 @@ sub task_child_stdout
 			# Build our submission, this is basically copying the hash
 			my %submission = %{$stats};
 			$submission{'timestamp'} = $timestamp;
+			$submission{'direction'} = $direction;
 
 			$logger->log(LOG_DEBUG,"[TCSTATS] Submitting stats for [%s]",$item);
 			$kernel->post("statistics" => "update" => $item => \%submission);
diff --git a/opentrafficshaper/plugins/webserver/pages/limits.pm b/opentrafficshaper/plugins/webserver/pages/limits.pm
new file mode 100644
index 0000000..022ad95
--- /dev/null
+++ b/opentrafficshaper/plugins/webserver/pages/limits.pm
@@ -0,0 +1,400 @@
+# 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
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package opentrafficshaper::plugins::webserver::pages::limits;
+
+use strict;
+use warnings;
+
+
+# Exporter stuff
+require Exporter;
+our (@ISA,@EXPORT,@EXPORT_OK);
+@ISA = qw(Exporter);
+@EXPORT = qw(
+);
+@EXPORT_OK = qw(
+);
+
+
+use DateTime;
+use HTML::Entities;
+use HTTP::Status qw( :constants );
+
+use opentrafficshaper::logger;
+use opentrafficshaper::plugins;
+use opentrafficshaper::utils;
+
+use opentrafficshaper::plugins::configmanager qw( getLimits getTrafficClasses getPriorityName );
+
+
+
+# Sidebar menu options for this module
+my $menu = {
+	'Limits' =>  {
+		'Show Limits' => '',
+	},
+	'Admin' => {
+		'Add Limit' => 'add',
+	},
+};
+
+
+
+# Default page/action
+sub default
+{
+	my ($kernel,$globals,$client_session_id,$request) = @_;
+
+
+	my $limits = getLimits();
+
+	# Build content
+	my $content = "";
+
+	# Header
+	$content .=<<EOF;
+<table class="table">
+	<legend>Limit List</legend>
+	<thead>
+		<tr>
+			<th></th>
+			<th>User</th>
+			<th>IP</th>
+			<th>Source</th>
+			<th>LastUpdate</th>
+			<th>Class</th>
+			<th>Group</th>
+			<th>Limits</th>
+			<th></th>
+		</tr>
+	</thead>
+	<tbody>
+EOF
+	# Body
+	foreach my $uid (keys %{$limits}) {
+		my $limit = $limits->{$uid};
+
+		# Make style a bit pretty
+		my $style = "";
+		my $icon = "";
+		if ($limit->{'Status'} eq "offline") {
+			$icon = '<i class="glyphicon-trash"></i>';
+			$style = "warning";
+		} elsif ($limit->{'Status'} eq "new") {
+#			$icon = '<i class="glyphicon-plus"></i>';
+			$style = "info";
+		} elsif ($limit->{'Status'} eq "conflict") {
+			$icon = '<i class="glyphicon-random"></i>';
+			$style = "error";
+		}
+
+		# Get a nice last update string
+		my $lastUpdate = DateTime->from_epoch( epoch => $limit->{'LastUpdate'} )->iso8601();
+		my $limits = $limit->{'TrafficLimitTx'} . "/" . $limit->{'TrafficLimitRx'};
+
+
+		# If the statistics plugin is loaded pull in some stats
+		my $statsPPSTx = my $statsRateTx = my $statsPrioTx = "unavail";
+		if (plugin_is_loaded('statistics')) {
+			my $stats = opentrafficshaper::plugins::statistics::getLastStats($limit->{'Username'});
+			# Pull off tx stats
+			if (my $statsTx = $stats->{'tx'}) {
+				$statsPPSTx = $statsTx->{'current_pps'};
+				$statsRateTx = $statsTx->{'current_rate'};
+				$statsPrioTx = getPriorityName($limit->{'TrafficPriority'});
+			}
+		}
+
+		$content .= <<EOF;
+		<tr class="$style">
+			<td>$icon</td>
+			<td class="limit">
+				$limit->{'Username'}
+				<span class="limit-data" style="display:none">
+					<table width="100%" border="0">
+						<tr>
+							<td>Tx Priority</td>
+							<td>$statsPrioTx</td>
+							<td>&nbsp;</td>
+							<td>Tx Priority</td>
+							<td>-</td>
+						</tr>
+						<tr>
+							<td>Tx PPS</td>
+							<td>$statsPPSTx</td>
+							<td>&nbsp;</td>
+							<td>Rx PPS</td>
+							<td>-</td>
+						</tr>
+						<tr>
+							<td>Tx Rate</td>
+							<td>$statsRateTx</td>
+							<td>&nbsp;</td>
+							<td>Rx Rate</td>
+							<td>-</td>
+						</tr>
+					</table>
+				</span>
+			</td>
+			<td>$limit->{'IP'}</td>
+			<td>$limit->{'Source'}</td>
+			<td>$lastUpdate</td>
+			<td>$limit->{'ClassID'}</td>
+			<td>$limit->{'GroupID'}</td>
+			<td>$limits</td>
+			<td>
+				<i class="glyphicon glyphicon-wrench"></i>
+				<i class="glyphicon glyphicon-stats"></i>
+			</td>
+		</tr>
+EOF
+	}
+	# No results
+	if (keys %{$limits} < 1) {
+		$content .=<<EOF;
+		<tr class="info">
+			<td colspan="8"><p class="text-center">No Results</p></td>
+		</tr>
+EOF
+	}
+
+	# Footer
+	$content .=<<EOF;
+	</tbody>
+</table>
+EOF
+
+	my $style = <<EOF;
+		.popover {
+			max-width:none;
+		}
+
+		.popover td:nth-child(4), .popover td:first-child {
+			font-weight:bold;
+			text-transform:capitalize;
+		}
+EOF
+
+	my $javascript = <<EOF;
+		\$(document).ready(function(){
+			\$('.limit').each(function(){
+				\$(this).popover({
+					html: true,
+					content: \$(this).find('.limit-data').html(),
+					placement: 'bottom',
+					trigger: 'hover',
+					container: \$(this),
+					title: 'Statistics',
+				});
+			})
+		});
+EOF
+
+	return (HTTP_OK,$content,{ 'style' => $style, 'menu' => $menu, 'javascript' => $javascript });
+}
+
+
+# Add action
+sub add
+{
+	my ($kernel,$globals,$client_session_id,$request) = @_;
+
+
+	# Setup our environment
+	my $logger = $globals->{'logger'};
+
+	# Errors to display
+	my @errors;
+	# Form items
+	my $params = {
+		'inputUsername' => undef,
+		'inputIP' => undef,
+		'inputTrafficClass' => undef,
+		'inputExpires' => undef,
+		'inputExpiresModifier' => undef,
+		'inputLimitTx' => undef,
+		'inputLimitRx' => undef,
+	};
+
+	# If this is a form try parse it
+	if ($request->method eq "POST") {
+		# Parse form data
+		$params = parseFormContent($request->content);
+
+		# If user pressed cancel, redirect
+		if (defined($params->{'cancel'})) {
+			# Redirects to default page
+			return (HTTP_TEMPORARY_REDIRECT,'limits');
+		}
+
+		# Check POST data
+		my $username;
+		if (!defined($username = isUsername($params->{'inputUsername'}))) {
+			push(@errors,"Username is not valid");
+		}
+		my $ipAddress;
+		if (!defined($ipAddress = isIP($params->{'inputIP'}))) {
+			push(@errors,"IP address is not valid");
+		}
+		my $trafficLimitTx;
+		if (!defined($trafficLimitTx = isNumber($params->{'inputLimitTx'}))) {
+			push(@errors,"Download limit is not valid");
+		}
+		my $trafficLimitRx;
+		if (!defined($trafficLimitRx = isNumber($params->{'inputLimitRx'}))) {
+			push(@errors,"Upload limit is not valid");
+		}
+
+		# If there are no errors we need to push this update
+		if (!@errors) {
+			# Build limit
+			my $limit = {
+				'Username' => $username,
+				'IP' => $ipAddress,
+				'GroupID' => 1,
+				'ClassID' => 1,
+				'TrafficLimitTx' => $trafficLimitTx,
+				'TrafficLimitRx' => $trafficLimitRx,
+				'TrafficLimitTxBurst' => $trafficLimitTx,
+				'TrafficLimitRxBurst' => $trafficLimitRx,
+				'Status' => "online",
+				'Source' => "plugin.webserver.limits",
+			};
+
+			# Throw the change at the config manager
+			$kernel->post("configmanager" => "process_change" => $limit);
+
+			$logger->log(LOG_INFO,"[WEBSERVER/LIMIS/ADD] User: $username, IP: $ipAddress, Group: 1, Class: 2, ".
+					"Limits: ".prettyUndef($trafficLimitTx)."/".prettyUndef($trafficLimitRx).", Burst: ".prettyUndef($trafficLimitTx)."/".prettyUndef($trafficLimitRx));
+
+			return (HTTP_TEMPORARY_REDIRECT,'limits');
+		}
+	}
+
+	# Sanitize params if we need to
+	foreach my $item (keys %{$params}) {
+		$params->{$item} = defined($params->{$item}) ? encode_entities($params->{$item}) : "";	
+	}
+
+	# Build content
+	my $content = "";
+
+	# Form header
+	$content .=<<EOF;
+<form role="form" method="post">
+	<legend>Add Manual Limit</legend>
+EOF
+
+	# Spit out errors if we have any
+	if (@errors > 0) {
+		foreach my $error (@errors) {
+			$content .= '<div class="alert alert-error">'.$error.'</div>';
+		}
+	}
+
+	# Generate traffic class list
+	my $trafficClasses = getTrafficClasses();
+	my $trafficClassStr = "";
+	foreach my $classID (keys %{$trafficClasses}) {
+		$trafficClassStr .= '<option value="'.$classID.'">'.$trafficClasses->{$classID}.'</option>';
+	}
+
+	# Header
+	$content .=<<EOF;
+	<div class="form-group">
+		<label for="inputUsername" class="col-lg-2 control-label">Username</label>
+		<div class="row">
+			<div class="col-lg-4">
+				<input name="inputUsername" type="text" placeholder="Username" class="form-control" value="$params->{'inputUsername'}" />
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<label for="inputIP" class="col-lg-2 control-label">IP Address</label>
+		<div class="row">
+			<div class="col-lg-4">
+				<input name="inputIP" type="text" placeholder="IP Address" class="form-control" value="$params->{'inputIP'}" />
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<label for="inputTafficClass" 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'}">
+					$trafficClassStr
+				</select>
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<label for="inputExpires" 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'}" />
+			</div>
+			<div class="col-lg-2">
+				<select name="inputExpiresModifier" placeholder="Expires Modifier" class="form-control" value="$params->{'inputExpiresModifier'}">
+					<option value="m">Mins</option>
+					<option value="h">Hours</option>
+					<option value="d">Days</option>
+				</select>
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<label for="inputLimitTx" class="col-lg-2 control-label">Download Limit</label>
+		<div class="row">
+			<div class="col-lg-3">
+				<div class="input-group">
+					<input name="inputLimitTx" type="text" placeholder="TX Limit" class="form-control" value="$params->{'inputLimitTx'}" />
+					<span class="input-group-addon">Kbps<span>
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<label for="inputLimitRx" class="col-lg-2 control-label">Upload Limit</label>
+		<div class="row">
+			<div class="col-lg-3">
+				<div class="input-group">
+					<input name="inputLimitRx" type="text" placeholder="RX Limit" class="form-control" value="$params->{'inputLimitRx'}" />
+					<span class="input-group-addon">Kbps<span>
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<label for="inputDescription" class="col-lg-2 control-label">Description</label>
+		<div class="row">
+			<div class="col-lg-4">
+				<textarea name="inputDescription" placeholder="Description" rows="3" class="form-control"></textarea>
+			</div>
+		</div>
+	</div>
+	<div class="form-group">
+		<button type="submit" class="btn btn-primary">Add</button>
+		<button name="cancel" type="submit" class="btn">Cancel</button>
+	</div>
+</form>
+EOF
+
+	return (HTTP_OK,$content,{ 'menu' => $menu });
+}
+
+
+1;
diff --git a/opentrafficshaper/plugins/webserver/pages/statistics.pm b/opentrafficshaper/plugins/webserver/pages/statistics.pm
new file mode 100644
index 0000000..349673f
--- /dev/null
+++ b/opentrafficshaper/plugins/webserver/pages/statistics.pm
@@ -0,0 +1,200 @@
+# OpenTrafficShaper webserver module: users 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
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package opentrafficshaper::plugins::webserver::pages::statistics;
+
+use strict;
+use warnings;
+
+
+# Exporter stuff
+require Exporter;
+our (@ISA,@EXPORT,@EXPORT_OK);
+@ISA = qw(Exporter);
+@EXPORT = qw(
+);
+@EXPORT_OK = qw(
+);
+
+
+use DateTime;
+use HTML::Entities;
+use HTTP::Status qw( :constants );
+
+use opentrafficshaper::logger;
+use opentrafficshaper::utils;
+
+use opentrafficshaper::plugins::statistics::statistics;
+
+# Default page/action
+sub default
+{
+	my ($kernel,$globals,$client_session_id,$request) = @_;
+
+
+	# Build content
+	my $content = <<EOF;
+		
+	<div id="header">
+		<h2>Traffic Shaper Stats</h2>
+	</div>
+
+	<div id="content" style="float:left">
+
+		<div style="position: relative; top:50px;">
+		<h4 style="color:#8f8f8f;">Json Data (loads from /static/stats.json.js)</h4>
+		<br/>	
+
+
+	    <div id="ajaxData" class="ajaxData" style="float:left; width:1024px; height: 560px"></div>
+        </div>
+
+	</div>
+
+EOF
+
+	# FIXME - Dynamic script inclusion required
+
+	#$content .= statistics::do_test();
+#	$content .= opentrafficshaper::plugins::statistics::do_test();
+
+	# Files loaded at end of HTML document
+	my @javascripts = (
+		'/static/awit-flot/jquery.flot.min.js',
+		'/static/awit-flot/jquery.flot.time.min.js',
+#		'/static/awit-flot/jquery.flot.websockets.js'
+	);
+
+	# String put in <script> </script> tags after the above files are loaded
+	my $javascript =<<EOF;
+	//
+    // Tooltip - Displays detailed information regarding the data point
+    //
+	function showTooltip(x, y, contents) {
+		jQuery('<div id="tooltip">' + contents + '</div>').css( {
+			position: 'absolute',
+			display: 'none',
+			top: y - 30,
+			left: x - 50,
+			color: "#fff",
+			padding: '2px 5px',
+			'border-radius': '6px',
+			'background-color': '#000',
+			opacity: 0.80
+		}).appendTo("body").fadeIn(200);
+	}
+
+	var previousPoint = null;
+
+	jQuery("#ajaxData").bind("plothover", function (event, pos, item) {
+        if (item) {
+            if (previousPoint != item.dataIndex) {
+                previousPoint = item.dataIndex;
+
+                jQuery("#tooltip").remove();
+                var x = item.datapoint[0].toFixed(0),
+                    y = item.datapoint[1].toFixed(0);
+
+                showTooltip(item.pageX, item.pageY,
+                            item.series.label + ' date: ' + month);
+            }
+        }
+        else {
+            jQuery("#tooltip").remove();
+            previousPoint = null;
+        }
+    });
+
+
+	// Setting up the graph here
+	options = {
+		series: {
+			lines: { show: true,
+					lineWidth: 1,
+					fill: true, 
+					fillColor: { colors: [ { opacity: 0.1 }, { opacity: 0.13 } ] }
+				 },
+			points: { show: true, 
+					 lineWidth: 2,
+					 radius: 3
+				 },
+			shadowSize: 0,
+			stack: true
+		},
+		grid: { 
+			hoverable: true, 
+			clickable: false, 
+			tickColor: "#f9f9f9",
+			borderWidth: 0
+		},
+		legend: {
+			// show: false
+			labelBoxBorderColor: "#fff"
+			//,container: '#legend-container'
+		},
+
+		xaxis: {
+			mode: "time",
+			tickSize: [2, "second"],
+			tickFormatter: function (v, axis) {
+				var date = new Date(v);
+		 		
+				if (date.getSeconds() % 1 == 0) {
+					var hours = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
+					var minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
+					var seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
+		 
+					return hours + ":" + minutes + ":" + seconds;
+				} else {
+					return "";
+				}
+			},
+		},
+		yaxis: {
+			min: 0,
+			max: 4000,
+			tickFormatter: function (v, axis) {
+				if (v % 10 == 0) {
+					return v;
+				} else {
+					return "";
+				}
+			},
+		}
+	}
+	//*/
+
+	// loading the stats.json.js file as data and drawing the graph on successful response.
+	jQuery.ajax({
+	  url: '/static/stats.json.js',
+	  dataType: "json",
+	  success: function(statsData){
+		plot = null;
+		if (statsData.length > 0) {
+			plot = jQuery.plot(jQuery("#ajaxData"), statsData, options);
+		}
+	  }
+	});
+
+
+EOF
+
+	return (HTTP_OK,$content,{ 'javascripts' => \@javascripts, 'javascript' => $javascript });
+}
+
+
+
+1;
diff --git a/opentrafficshaper/plugins/webserver/pages/users.pm b/opentrafficshaper/plugins/webserver/pages/users.pm
deleted file mode 100644
index 466fc7f..0000000
--- a/opentrafficshaper/plugins/webserver/pages/users.pm
+++ /dev/null
@@ -1,278 +0,0 @@
-# OpenTrafficShaper webserver module: users 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
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package opentrafficshaper::plugins::webserver::pages::users;
-
-use strict;
-use warnings;
-
-
-# Exporter stuff
-require Exporter;
-our (@ISA,@EXPORT,@EXPORT_OK);
-@ISA = qw(Exporter);
-@EXPORT = qw(
-);
-@EXPORT_OK = qw(
-);
-
-
-use DateTime;
-use HTML::Entities;
-use HTTP::Status qw( :constants );
-
-use opentrafficshaper::logger;
-use opentrafficshaper::utils;
-
-
-
-# Sidebar menu options for this module
-my $menu = {
-	'Users' =>  {
-		'Show Users' => '',
-	},
-	'Admin' => {
-		'Add User' => 'add',
-	},
-};
-
-
-
-# Default page/action
-sub default
-{
-	my ($kernel,$globals,$client_session_id,$request) = @_;
-
-
-	my $users = $globals->{'users'};
-
-	# Build content
-	my $content = "";
-
-	# Header
-	$content .=<<EOF;
-<table class="table">
-	<legend>User List</legend>
-	<thead>
-		<tr>
-			<th>#</th>
-			<th>User</th>
-			<th>IP</th>
-			<th>Source</th>
-			<th>LastUpdate</th>
-			<th>Class</th>
-			<th>Group</th>
-			<th>Limits</th>
-		</tr>
-	</thead>
-	<tbody>
-EOF
-	# Body
-	foreach my $uid (keys %{$users}) {
-		my $user = $users->{$uid};
-
-		# Make style a bit pretty
-		my $style = "";
-		my $icon = "";
-		if ($user->{'Status'} eq "offline") {
-			$icon = '<i class="icon-trash"></i>';
-			$style = "warning";
-		} elsif ($user->{'Status'} eq "new") {
-#			$icon = '<i class="icon-plus"></i>';
-			$style = "info";
-		} elsif ($user->{'Status'} eq "conflict") {
-			$icon = '<i class="icon-random"></i>';
-			$style = "error";
-		}
-
-		# Get a nice last update string
-		my $lastUpdate = DateTime->from_epoch( epoch => $user->{'LastUpdate'} )->iso8601();
-		my $limits = $user->{'TrafficLimitTx'} . "/" . $user->{'TrafficLimitRx'};
-
-		$content .=<<EOF;
-		<tr class="$style">
-			<td>$icon</td>
-			<td>$user->{'Username'}</td>
-			<td>$user->{'IP'}</td>
-			<td>$user->{'Source'}</td>
-			<td>$lastUpdate</td>
-			<td>$user->{'ClassID'}</td>
-			<td>$user->{'GroupID'}</td>
-			<td>$limits</td>
-		</tr>
-EOF
-	}
-	# No results
-	if (keys %{$globals->{'users'}} < 1) {
-		$content .=<<EOF;
-		<tr class="info">
-			<td colspan="8"><p class="text-center">No Results</p></td>
-		</tr>
-EOF
-	}
-
-	# Footer
-	$content .=<<EOF;
-	</tbody>
-</table>
-EOF
-
-
-	return (HTTP_OK,$content,$menu);
-}
-
-
-# Add action
-sub add
-{
-	my ($kernel,$globals,$client_session_id,$request) = @_;
-
-
-	# Setup our environment
-	my $logger = $globals->{'logger'};
-
-	# Errors to display
-	my @errors;
-	# Form items
-	my $params = {
-		'inputUsername' => undef,
-		'inputIP' => undef,
-		'inputLimitTx' => undef,
-		'inputLimitRx' => undef,
-	};
-	
-	# If this is a form try parse it
-	if ($request->method eq "POST") {
-		# Parse form data
-		$params = parseFormContent($request->content);
-
-		# If user pressed cancel, redirect
-		if (defined($params->{'cancel'})) {
-			# Redirects to default page
-			return (HTTP_TEMPORARY_REDIRECT,'users');
-		}
-
-		# Check POST data
-		my $username;
-		if (!defined($username = isUsername($params->{'inputUsername'}))) {
-			push(@errors,"Username is not valid");
-		}
-		my $ipAddress;
-		if (!defined($ipAddress = isIP($params->{'inputIP'}))) {
-			push(@errors,"IP address is not valid");
-		}
-		my $trafficLimitTx;
-		if (!defined($trafficLimitTx = isNumber($params->{'inputLimitTx'}))) {
-			push(@errors,"Download limit is not valid");
-		}
-		my $trafficLimitRx;
-		if (!defined($trafficLimitRx = isNumber($params->{'inputLimitRx'}))) {
-			push(@errors,"Upload limit is not valid");
-		}
-
-		# If there are no errors we need to push this update
-		if (!@errors) {
-			# Build user
-			my $user = {
-				'Username' => $username,
-				'IP' => $ipAddress,
-				'GroupID' => 1,
-				'ClassID' => 1,
-				'TrafficLimitTx' => $trafficLimitTx,
-				'TrafficLimitRx' => $trafficLimitRx,
-				'TrafficLimitTxBurst' => $trafficLimitTx,
-				'TrafficLimitRxBurst' => $trafficLimitRx,
-				'Status' => "online",
-				'Source' => "plugin.webserver.users",
-			};
-
-			# Throw the change at the config manager
-			$kernel->post("configmanager" => "process_change" => $user);
-
-			$logger->log(LOG_INFO,"[WEBSERVER/USERS/ADD] User: $username, IP: $ipAddress, Group: 1, Class: 2, ".
-					"Limits: ".prettyUndef($trafficLimitTx)."/".prettyUndef($trafficLimitRx).", Burst: ".prettyUndef($trafficLimitTx)."/".prettyUndef($trafficLimitRx));
-
-			return (HTTP_TEMPORARY_REDIRECT,'users');
-		}
-	}
-
-	# Sanitize params if we need to
-	foreach my $item (keys %{$params}) {
-		$params->{$item} = defined($params->{$item}) ? encode_entities($params->{$item}) : "";	
-	}
-
-	# Build content
-	my $content = "";
-
-	# Form header
-	$content .=<<EOF;
-<form class="form-horizontal" method="post">
-	<legend>Add Manual User</legend>
-EOF
-
-	# Spit out errors if we have any
-	if (@errors > 0) {
-		foreach my $error (@errors) {
-			$content .= '<div class="alert alert-error">'.$error.'</div>';
-		}
-	}
-
-	# Header
-	$content .=<<EOF;
-	<div class="control-group">
-		<label class="control-label" for="inputUsername">Username</label>
-		<div class="controls">
-			<input name="inputUsername" type="text" placeholder="Username" value="$params->{'inputUsername'}" />
-		</div>
-	</div>
-	<div class="control-group">
-		<label class="control-label" for="inputIP">IP Address</label>
-		<div class="controls">
-			<input name="inputIP" type="text" placeholder="IP Address" value="$params->{'inputIP'}" />
-		</div>
-	</div>
-	<div class="control-group">
-		<label class="control-label" for="inputLimitTx">Download Limit</label>
-		<div class="controls">
-			<div class="input-append">
-				<input name="inputLimitTx" type="text" class="span5" placeholder="TX Limit" value="$params->{'inputLimitTx'}" />
-				<span class="add-on">Kbps<span>
-			</div>
-		</div>
-	</div>
-	<div class="control-group">
-		<label class="control-label" for="inputLimitRx">Upload Limit</label>
-		<div class="controls">
-			<div class="input-append">
-				<input name="inputLimitRx" type="text" class="span5" placeholder="RX Limit" value="$params->{'inputLimitRx'}" />
-				<span class="add-on">Kbps<span>
-			</div>
-		</div>
-	</div>
-	<div class="control-group">
-		<div class="controls">
-			<button type="submit" class="btn btn-primary">Add</button>
-			<button name="cancel" type="submit" class="btn">Cancel</button>
-		</div>
-	</div>
-</form>
-EOF
-
-	return (200,$content,$menu);
-}
-
-
-1;
diff --git a/opentrafficshaper/plugins/webserver/snapins/websockets/statistics/statistics.pm b/opentrafficshaper/plugins/webserver/snapins/websockets/statistics/statistics.pm
index 6d7abc4..7acbf8b 100644
--- a/opentrafficshaper/plugins/webserver/snapins/websockets/statistics/statistics.pm
+++ b/opentrafficshaper/plugins/webserver/snapins/websockets/statistics/statistics.pm
@@ -113,7 +113,7 @@ sub session_init
 }
 
 
-# Signal that the client has connected
+# Send data to client
 sub do_send
 {
 	my ($kernel,$heap,$uid,$data) = @_[KERNEL, HEAP, ARG0, ARG1];
@@ -125,6 +125,8 @@ sub do_send
 
 		use Data::Dumper; print STDERR "Got request to send client '$client_session_id': ".Dumper($data);
 
+#		my $json = sprintf('{"label": "%s", data: [%s] }', );
+
 		$socket->put("hello there");
 	}
 }
diff --git a/opentrafficshaper/plugins/webserver/webserver.pm b/opentrafficshaper/plugins/webserver/webserver.pm
index 378fac7..83e22c4 100644
--- a/opentrafficshaper/plugins/webserver/webserver.pm
+++ b/opentrafficshaper/plugins/webserver/webserver.pm
@@ -35,7 +35,8 @@ use opentrafficshaper::plugins;
 # Pages (this is used a little below)
 use opentrafficshaper::plugins::webserver::pages::static;
 use opentrafficshaper::plugins::webserver::pages::index;
-use opentrafficshaper::plugins::webserver::pages::users;
+use opentrafficshaper::plugins::webserver::pages::limits;
+use opentrafficshaper::plugins::webserver::pages::statistics;
 
 
 # Exporter stuff
@@ -84,9 +85,12 @@ my $resources = {
 		'static' => {
 			'_catchall' => \&opentrafficshaper::plugins::webserver::pages::static::_catchall,
 		},
-		'users' => {
-			'default' => \&opentrafficshaper::plugins::webserver::pages::users::default,
-			'add' => \&opentrafficshaper::plugins::webserver::pages::users::add,
+		'limits' => {
+			'default' => \&opentrafficshaper::plugins::webserver::pages::limits::default,
+			'add' => \&opentrafficshaper::plugins::webserver::pages::limits::add,
+		},
+		'statistics' => {
+			'default' => \&opentrafficshaper::plugins::webserver::pages::statistics::default,
 		},
 	},
 };
@@ -280,7 +284,7 @@ sub httpRedirect
 # Create a response object
 sub httpCreateResponse
 {
-	my ($module,$content,$menu) = @_;
+	my ($module,$content,$options) = @_;
 
 
 	# Throw out message to client to authenticate first
@@ -289,41 +293,58 @@ sub httpCreateResponse
 
 
 	# Check if we have a menu structure, if we do, display the sidebar
+	my $styleStr = "";
 	my $menuStr = "";
-	if (defined($menu)) {
-		$menuStr =<<EOF;
-			<div class="span2">
-				<div class="well sidebar-nav">
-					<ul class="nav nav-list">
+	my $javascriptStr = "";
+	if (defined($options)) {
+		# Check if style snippet exists
+		if (defined(my $style = $options->{'style'})) {
+			$styleStr .= $style;
+		}
+
+		# Check if menu exists
+		if (my $menu = $options->{'menu'}) {
+			$menuStr =<<EOF;
+				<ul class="nav nav-pills nav-stacked">
 EOF
-		# Loop with sub menu sections
-		foreach my $section (keys %{$menu}) {
-#							<li class="nav-header">Sidebar</li>
-#							<li class="active"><a href="#">Link</a></li>
-#							<li><a href="#">Link</a></li>
-#							<li class="nav-header">Sidebar</li>
-#							<li><a href="#">Link</a></li>
-			# Loop with menu items
-			foreach my $item (keys %{$menu->{$section}}) {
-				my $link = "/" . $module . "/" . $menu->{$section}->{$item};
-				# Sanitize slightly
-				$link =~ s,/+$,,;
-
-				# Build sections
-				$menuStr .=<<EOF;
+			# Loop with sub menu sections
+			foreach my $section (keys %{$menu}) {
+				# Loop with menu items
+				foreach my $item (keys %{$menu->{$section}}) {
+					my $link = "/" . $module . "/" . $menu->{$section}->{$item};
+					# Sanitize slightly
+					$link =~ s,/+$,,;
+
+					# Build sections
+					$menuStr .=<<EOF;
 						<li class="nav-header">$section</li>
 						<li><a href="$link">$item</a></li>
 EOF
+				}
 			}
+			$menuStr .=<<EOF;
+				</ul>
+EOF
 		}
-		$menuStr .=<<EOF;
-					</ul>
-				</div><!--/.well -->
-			</div><!--/span-->
+
+		# Check if we have a list of javascript assets
+		if (defined(my $javascripts = $options->{'javascripts'})) {
+			foreach my $script (@{$javascripts}) {
+				$javascriptStr .=<<EOF;
+					<script type="text/javascript" src="$script"></script>
 EOF
+			}
+		}
+		# Check if javascript snippet exists
+		if (defined(my $javascript = $options->{'javascript'})) {
+			$javascriptStr .=<<EOF;
+				<script type="text/javascript">
+$javascript
+				</script>
+EOF
+		}
 	}
 
-
 	# Build action response
 	my $resp = HTTP::Response->new(
 			HTTP_OK,"Ok",
@@ -335,72 +356,68 @@ EOF
 		<meta name="viewport" content="width=device-width, initial-scale=1.0">
 		<!-- Assets -->
 		<link href="/static/favicon.ico" rel="icon" />
-		<link href="/static/jquery-ui/css/ui-lightness/jquery-ui.min.css" rel="stylesheet" media="screen">
-		<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
+		<link href="/static/jquery-ui/css/ui-lightness/jquery-ui.min.css" rel="stylesheet">
+		<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
 
 		<style type="text/css">
 			body {
-				padding-top: 60px;
-				padding-bottom: 40px;
+				padding-top: 50px;
 			}
-			.sidebar-nav {
-				padding: 9px 0;
-			}
-			\@media (max-width: 980px) {
-				/* Enable use of floated navbar text */
-				.navbar-text.pull-right {
-					float: none;
-					padding-left: 5px;
-					padding-right: 5px;
-				}
+
+			.main-area {
+				padding-top: 15px;
+				padding-bottom: 15px;
 			}
+$styleStr
 		</style>
-
 		<!-- End Assets -->
-		<link href="/static/bootstrap/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen">
 	</head>
-	<body>
 
+	<body>
 		<div class="navbar navbar-inverse navbar-fixed-top">
-			<div class="navbar-inner">
-				<div class="container-fluid">
-					<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+			<div class="container">
+				<div class="navbar-header">
+					<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
 						<span class="icon-bar"></span>
 						<span class="icon-bar"></span>
 						<span class="icon-bar"></span>
 					</button>
-   					<a class="brand" href="/">OpenTrafficShaper</a>
-					<div class="nav-collapse collapse">
-						<p class="navbar-text pull-right">Logged in as <a href="#" class="navbar-link">Username</a>	</p>
-						<ul class="nav">
-							<li class="active"><a href="#">Home</a></li>
-							<li><a href="/users">Users</a></li>
-						</ul>
-					</div><!--/.nav-collapse -->
+   					<a class="navbar-brand" href="/">OpenTrafficShaper</a>
+				</div>
+				<div class="collapse navbar-collapse">
+					<ul class="nav navbar-nav">
+						<li class="active"><a href="#">Home</a></li>
+						<li><a href="/limits">Limits</a></li>
+					</ul>
 				</div>
 			</div>
 		</div>
 
-		<div class="container-fluid">
-			<div class="row-fluid">
-					$menuStr
-				<div class="span10">
-					$content
-				</div><!--/span-->
-			</div><!--/row-->
+		<div class="main-area container">
+				<div class="col-md-2">
+$menuStr
+				</div>
+				<div class="col-md-10">
+$content
+				</div>
+			</div>
 			<hr>
 			<footer>
 				<p class="muted">v$globals->{'version'} - Copyright &copy; 2013,  <a href="http://www.allworldit.com">AllWorldIT</a></p>
 			</footer>
-		</div><!--/.fluid-container-->
+		</div>
+
+	</body>
+
+
+	<script type="text/javascript" src="/static/jquery/js/jquery.min.js"></script>
+	<script type="text/javascript" src="/static/jquery-ui/js/jquery-ui.min.js"></script>
+	<script type="text/javascript" src="/static/bootstrap/js/bootstrap.min.js"></script>
+$javascriptStr
 
-		<!-- Javascript -->
-		<script src="/static/jquery/js/jquery.min.js"></script>
-		<script src="/static/jquery-ui/js/jquery-ui.min.js"></script>
-		<script src="/static/bootstrap/js/bootstrap.min.js"></script>
-  </body>
 </html>
 EOF
+	## FIXME - Dyanmic script inclusion required
 	return $resp;
 }
 
@@ -551,11 +568,11 @@ sub _parse_http_resource
 	my (undef,$dmodule,$daction) = $request->uri->path_segments();
 	# If any is blank, set it to the default
 	$dmodule = "index" if (!defined($dmodule) || $dmodule eq "");
-	$daction = "default" if (!defined($daction));
+	$daction = "default" if (!defined($daction) || $daction eq "");
 	# Sanitize
 	(my $module = $dmodule) =~ s/[^A-Za-z0-9]//g;	
 	(my $action = $daction) =~ s/[^A-Za-z0-9]//g;	
-
+print STDERR "module = $module, action = $action\n";
 	# If module name is sneaky? then just block it
 	if ($module ne $dmodule) {
 		return httpDisplayFault(HTTP_FORBIDDEN,"Method Not Allowed","The requested resource '$module' is not allowed.");
-- 
GitLab