diff --git a/lsp/functions.js b/lsp/functions.js
index 81993f1d..30ba1a0f 100644
--- a/lsp/functions.js
+++ b/lsp/functions.js
@@ -1,18 +1,18 @@
 
-			/**
-			 * Show a confirm dialog
-			 * @param question the question displayed
-			 */
+         /**
+          * Show a confirm dialog
+          * @param question the question displayed
+          */
          function confirmDelete(question)
          {
             return confirm(question);
          }
 
 
-			/**
-			 * Format a date to mm/dd/yyyy hh:mm:ss format
-			 * @param date the date to format (timestamp)
-			 */
+         /**
+          * Format a date to mm/dd/yyyy hh:mm:ss format
+          * @param date the date to format (timestamp)
+          */
          function formatDate(date)
          {
             var d = new Date(date * 1000);
@@ -29,10 +29,10 @@
          }
 
 
-			/**
-			 * Find out what kind of resource an URI is
-			 * @param uri the URI to check. If it start with a protocol (ebut not file://) return 'Live', else 'Recorded'
-			 */
+         /**
+          * Find out what kind of resource an URI is
+          * @param uri the URI to check. If it start with a protocol (ebut not file://) return 'Live', else 'Recorded'
+          */
          function TypeofResource(uri)
          {
             var protocol = /([a-zA-Z]+):\/\//.exec(uri);
@@ -46,10 +46,10 @@
          }
 
 
-			/**
-			 * convert a short limit name to a long one using the table above
-			 * @param name the short name of a limit
-			 */
+         /**
+          * convert a short limit name to a long one using the table above
+          * @param name the short name of a limit
+          */
          function shortToLongLimit(name)
          {
             var i;
@@ -66,13 +66,13 @@
          }
 
 
-			/**
-			 * forse the server to save to the config file
-			 * @param callback function to call after the command is send
-			 */
+         /**
+          * forse the server to save to the config file
+          * @param callback function to call after the command is send
+          */
          function forceJSONSave(callback)
          {
-				// build the object to send to the server
+            // build the object to send to the server
             var data =
             {
                'authorize':
@@ -83,7 +83,7 @@
                'save': 1
             };
 
-				// make the XHR call
+            // make the XHR call
             $.ajax(
             {
                'url': settings.server,
@@ -99,11 +99,11 @@
          }
 
 
-			/**
-			 * retrieves data from the server ;)
-			 * note: does not authenticate first. Assumes user is logged in.
-			 * @param callback the function to call when the data has been retrieved. This callback has 1 parameter, the data retrieved.
-			 */
+         /**
+          * retrieves data from the server ;)
+          * note: does not authenticate first. Assumes user is logged in.
+          * @param callback the function to call when the data has been retrieved. This callback has 1 parameter, the data retrieved.
+          */
          function getData(callback)
          {
             var data =
@@ -113,7 +113,7 @@
                   'username': settings.credentials.username,
                   'password': (settings.credentials.authstring != "" ? MD5(MD5(settings.credentials.password) + settings.credentials.authstring) : "" )
                },
-			   'capabilities': {}
+            'capabilities': {}
             };
 
             $.ajax(
@@ -132,11 +132,12 @@
                   var ret = $.extend(true,
                   {
                      "streams": {},
-					 "capabilities": {},
+							"capabilities": {},
                      "statistics": {}
                   }, d);
-
-						console.log('[651] RECV', ret);
+                  
+                  //IE breaks if the console isn't opened, so keep commented when committing
+                  //console.log('[651] RECV', ret);
 
                   if(callback)
                   {
@@ -147,113 +148,113 @@
          }
 
 
-			/**
-			 * retrieved the status and number of viewers from all streams
-			 * @param callback function that is called when the data is collected. Has one parameter, the data retrieved
-			 */
-			function getStreamsData(callback)
-			{
+         /**
+          * retrieved the status and number of viewers from all streams
+          * @param callback function that is called when the data is collected. Has one parameter, the data retrieved
+          */
+         function getStreamsData(callback)
+         {
 
-				getData(function(data)
-				{
-					var streams = {};   // streamID: [status, numViewers];
-					var cnt = 0;
+            getData(function(data)
+            {
+               var streams = {};   // streamID: [status, numViewers];
+               var cnt = 0;
 
-					for(var stream in data.streams)
-					{
-						streams[stream] = [data.streams[stream].online, 0];
-						cnt++;
-					}
+               for(var stream in data.streams)
+               {
+                  streams[stream] = [data.streams[stream].online, 0];
+                  cnt++;
+               }
 
-					if(cnt === 0)
-					{
-						return;   // if there are no streams, don't collect data and just return
-					}
+               if(cnt === 0)
+               {
+                  return;   // if there are no streams, don't collect data and just return
+               }
 
-					for(stream in data.statistics)
-					{
-						if(data.statistics[stream].curr)
-						{
-							for(var viewer in data.statistics[stream].curr)
-							{
-								streams[stream][1]++;
-							}
-						}
-					}
+               for(stream in data.statistics)
+               {
+                  if(data.statistics[stream].curr)
+                  {
+                     for(var viewer in data.statistics[stream].curr)
+                     {
+                        streams[stream][1]++;
+                     }
+                  }
+               }
 
-					callback(streams);
-				});
-			}
+               callback(streams);
+            });
+         }
 
 
-			/**
-			 * parses an url and returns the parts of it.
-			 * @return object containing the parts of the URL: protocol, host and port.
-			 */
+         /**
+          * parses an url and returns the parts of it.
+          * @return object containing the parts of the URL: protocol, host and port.
+          */
          function parseURL(url)
-			{
-				var pattern = /(https?)\:\/\/([^:\/]+)\:(\d+)?/i;
-				
-				var retobj = {protocol: '', host: '', port: ''};
-				var results = url.match(pattern);
+         {
+            var pattern = /(https?)\:\/\/([^:\/]+)\:(\d+)?/i;
+            
+            var retobj = {protocol: '', host: '', port: ''};
+            var results = url.match(pattern);
 
-				if(results != null)
-				{
-			      retobj.protocol = results[1];
-					retobj.host = results[2];
-					retobj.port = results[3];
-				}
+            if(results != null)
+            {
+               retobj.protocol = results[1];
+               retobj.host = results[2];
+               retobj.port = results[3];
+            }
 
-				return retobj;
-			}
+            return retobj;
+         }
 
-			/**
-			 * go figure.
-			 * @return true if there is a HTTP connector... and false if there isn't.
-			 */
-			function isThereAHTTPConnector()
-			{
+         /**
+          * go figure.
+          * @return true if there is a HTTP connector... and false if there isn't.
+          */
+         function isThereAHTTPConnector()
+         {
             var i,
                 len = (settings.settings.config.protocols ? settings.settings.config.protocols.length : 0);
 
-				for(i = 0; i < len; i++)
-				{
-					if(settings.settings.config.protocols[i].connector == 'HTTP')
-					{
-						return true;
-					}
-				}
+            for(i = 0; i < len; i++)
+            {
+               if(settings.settings.config.protocols[i].connector == 'HTTP')
+               {
+                  return true;
+               }
+            }
 
-				return false;
-			}
+            return false;
+         }
 
 
-			/**
-			 * retrieve port of the http connector
-			 * @return the port number
-			 */
-			function getHTTPControllerPort()
-			{
+         /**
+          * retrieve port of the http connector
+          * @return the port number
+          */
+         function getHTTPControllerPort()
+         {
             var i,
                 len = (settings.settings.config.protocols ? settings.settings.config.protocols.length : 0);
 
-				for(i = 0; i < len; i++)
-				{
-					if(settings.settings.config.protocols[i].connector == 'HTTP')
-					{
-						return settings.settings.config.protocols[i].port;
-					}
-				}
+            for(i = 0; i < len; i++)
+            {
+               if(settings.settings.config.protocols[i].connector == 'HTTP')
+               {
+                  return settings.settings.config.protocols[i].port;
+               }
+            }
 
-				return 0;
-			}
+            return 0;
+         }
 
 
 
-			/**
-			 * retrieves the stream status (online and total number of streams) and viewer info (total number of viewers).
-			 * @param callback function that is called when data is retrieved. Has one parameter, the retrieved data.
-			 */
+         /**
+          * retrieves the stream status (online and total number of streams) and viewer info (total number of viewers).
+          * @param callback function that is called when data is retrieved. Has one parameter, the retrieved data.
+          */
          function getStatData(callback)
          {
             getData(function(data)
@@ -290,14 +291,14 @@
          }
 
 
-			/**
-			 * Connect to the server and retrieve the data
-			 * @param callback the function to call when connected. Has one parameter, an optional error string.
-			 */
+         /**
+          * Connect to the server and retrieve the data
+          * @param callback the function to call when connected. Has one parameter, an optional error string.
+          */
          function loadSettings(callback)
          {
-				// display 'loading, please wait' while retrieving data
-				$('body').append( $('<div>').attr('id', 'shield').text('Loading, please wait...') );
+            // display 'loading, please wait' while retrieving data
+            $('body').append( $('<div>').attr('id', 'shield').text('Loading, please wait...') );
 
             var errorstr = '',
                 data = $.extend(settings.settings,
@@ -311,8 +312,9 @@
 
             delete data.log;   // don't send the logs back to the server
             delete data.statistics;   // same goes for the stats
-
-            console.log('[763] SEND', data);
+            
+            //IE breaks if the console isn't opened, so keep commented when committing
+            //console.log('[763] SEND', data);
 
             $.ajax(
             {
@@ -328,13 +330,14 @@
                'error': function()
                {
                   showTab('disconnect');
-						$('#shield').remove();   // remove loading display
+                  $('#shield').remove();   // remove loading display
                },
                'success': function(d)
                {
-						$('#shield').remove();   // remove loading display
+                  $('#shield').remove();   // remove loading display
 
-                  console.log('[785] RECV', d);
+                  //IE breaks if the console isn't opened, so keep commented when committing
+                  //console.log('[785] RECV', d);
 
                   if(d && d['authorize'] && d['authorize']['challenge'])
                   {
@@ -358,7 +361,7 @@
                            "version": ""
                         },
                         "streams": {},
-						"capabilities": {},
+                  "capabilities": {},
                         "log": {},
                         "statistics": {}
                      }, d);
@@ -372,10 +375,10 @@
          }
 
 
-			/**
-			 * Sets the page's header text (loging in, connected, disconnected), title and pretty colors (!)
-			 * @param state the state of the header. Possible are 'logingin', 'disconnected' or 'connected'.
-			 */
+         /**
+          * Sets the page's header text (loging in, connected, disconnected), title and pretty colors (!)
+          * @param state the state of the header. Possible are 'logingin', 'disconnected' or 'connected'.
+          */
          function setHeaderState(state)
          {
             var text, cname, title;
@@ -396,22 +399,22 @@
 
 
 
-			/**
-			 * Formats the status property to a string (with colors!)
-			 * @param status, the status property of a stream
-			 */
+         /**
+          * Formats the status property to a string (with colors!)
+          * @param status, the status property of a stream
+          */
          function formatStatus(status)
          {
-				if(status == undefined)
-				{
-					return "<span>Unknown, checking...</span>";
-				}
+            if(status == undefined)
+            {
+               return "<span>Unknown, checking...</span>";
+            }
 
-				switch(status)
-				{
-					case 1:		return "<span class='green'>Running</span>";				break;
-					case 0:		return "<span class='red'>Offline</span>";				break;
-					default:		return "<span class='green'>" + status + "</span>";	break;
-				}
+            switch(status)
+            {
+               case 1:		return "<span class='green'>Running</span>";				break;
+               case 0:		return "<span class='red'>Offline</span>";				break;
+               default:		return "<span class='green'>" + status + "</span>";	break;
+            }
          }
 
diff --git a/lsp/graphics/sort.png b/lsp/graphics/sort.png
new file mode 100644
index 00000000..b0802bd3
Binary files /dev/null and b/lsp/graphics/sort.png differ
diff --git a/lsp/graphics/sort_asc.png b/lsp/graphics/sort_asc.png
new file mode 100644
index 00000000..fcc1b805
Binary files /dev/null and b/lsp/graphics/sort_asc.png differ
diff --git a/lsp/graphics/sort_desc.png b/lsp/graphics/sort_desc.png
new file mode 100644
index 00000000..340def41
Binary files /dev/null and b/lsp/graphics/sort_desc.png differ
diff --git a/lsp/main.js b/lsp/main.js
index 7360859f..a5f1b5f5 100644
--- a/lsp/main.js
+++ b/lsp/main.js
@@ -1,15 +1,15 @@
 
-			/**
-			 * Local settings page
-			 * DDVTECH
-			 * for more information, see http://mistserver.org/index.php?title=Stand-alone_Configuration_Page
-			 */
-
-
-			/**
-			 * Store a local copy of the data send by the server.
-			 * server is the URL, credentials hold the username, password and authstring and settings is the local copy.
-			 */
+      /**
+       * Local settings page
+       * DDVTECH
+       * for more information, see http://mistserver.org/index.php?title=Stand-alone_Configuration_Page
+       */
+       
+       
+      /**
+       * Store a local copy of the data send by the server.
+       * server is the URL, credentials hold the username, password and authstring and settings is the local copy.
+       */
          var settings =
          {
             server: '',
@@ -21,11 +21,11 @@
             },
             settings: {}
          };
-
-
-			/**
-			 *  Table for long/short limit names
-			 */
+         
+         
+      /**
+       *  Table for long/short limit names
+       */
          var ltypes =
          [
             ['kb_total', 'Total bandwidth'],
@@ -39,11 +39,11 @@
             ['str_kbps_min', 'Minimum bitrate'],
             ['str_kbps_max', 'Maximum bitrate']
          ];
-
-
-			/**
-			 *  When the page loads, fix the menu and show the correct tab
-			 */
+         
+         
+      /**
+       *  When the page loads, fix the menu and show the correct tab
+       */
          $(document).ready(function()
          {
             $('#nav').children().each(function()
@@ -55,71 +55,73 @@
                   {
                      $(this).attr('class', '');
                   });
-
+                  
                   // select this one
                   $(this).attr('class', 'selected');
-
+                  
                   // show correct tab
                   showTab($(this).text());
                });
             });
-
+            
             // show login 'tab' and hide menu
             showTab('login');
             $('#nav').css('visibility', 'hidden');
          });
-
-
-
+         
+         
          // used on the overview and streams page. cleared when switching to another 'tab'.
          var sinterval = null;
-
+         
+         // and this one is used on the protocols page.
+         var pinterval = null;
+         
          // what kind of streams should be displayed? Format is [recorded, live];
          var streamsdisplay = [true, true];
-
-
-
-			/**
-			 * Display a certain page. It contains a (giant) switch-statement, that builds a page depending on the tab requested
-			 * @param name the name of the tab
-			 * @param streamname only used when editing streams, the name of the edited (or new) stream. Also used with the 'embed' tab
-			 */
+         
+         
+      /**
+       * Display a certain page. It contains a (giant) switch-statement, that builds a page depending on the tab requested
+       * @param name the name of the tab
+       * @param streamname only used when editing streams, the name of the edited (or new) stream. Also used with the 'embed' tab
+       */
          function showTab(name, streamname)
          {
             // clear page and refresh interval
             $('#page').html('');
             clearInterval(sinterval);
-
+            clearInterval(pinterval);
+            
             switch(name)
             {
                case 'login':
-
+               
                   var host = $('<input>').attr('type', 'text').attr('placeholder', 'HTTP://' + (location.host == '' ? 'localhost:4242' : location.host) + '/api');
                   var user = $('<input>').attr('type', 'text').attr('placeholder', 'USERNAME');
                   var pass = $('<input>').attr('type', 'password').attr('placeholder', 'PASSWORD');
-                  var conn = $('<button>').click(function()
+                  var conn = $('<button>').click(function()	
                   {
                      // get login info
                      settings.credentials.username = user.val();
                      settings.credentials.password = pass.val();
                      settings.server = host.val() == '' ? host.attr('placeholder') : host.val().toLowerCase();
-
+                     
                      // save username, URL in address
                      location.hash = user.val() + '@' + settings.server;
-
+                     
                      // try to login
                      setHeaderState('logingin');
-
+                     
                      loadSettings(function(errorstr)
                      {
                         if(errorstr == '')
                         {
                            setHeaderState('connected');
-
+                           
                            $('#nav').css('visibility', 'visible');
-
+                           
                            showTab('overview');
-
+                           
                            // show overview as current tab - this only happens when logging out and then in
                            $('#nav').children().each(function()
                            {
@@ -130,18 +132,19 @@
                                  $(this).attr('class', 'selected');
                               }
                            });
-
+                           
                         }else{
                            setHeaderState('disconnected');
                            $('#header-host').text('');
+									$('#page').append($('<p>').text(errorstr));
                         }
                      });
                   }).text('login');
-
+                  
                   $('#page').append(
                      $('<div>').attr('id', 'login').append(host).append(user).append(pass).append(conn)
                   );
-
+                  
                   // if we 'enter' in host, user or pass we should try to login.
                   function hand(e)
                   {
@@ -150,28 +153,25 @@
                         conn.trigger('click');  // conn = login button
                      }
                   }
-
+                  
                   host.keypress(hand);
                   user.keypress(hand);
                   pass.keypress(hand);
-
+                  
                   // retrieve address hash from URL
                   var adr = location.hash.replace('#', '').split('@');
-
+                  
                   if(adr.length == 2)
                   {
                      // put it in the page
                      host.val(adr[1]);
                      user.val(adr[0]);
                   }
-
+                  
                   break;
-
-
-
-
+                  
                case 'overview':
-
+                  
                   $('#page').append(
                      $('<div>').attr('id', 'editserver').append(
                         $('<label>').attr('for', 'config-host').text('host').append(
@@ -199,7 +199,7 @@
                         )
                      )
                   );
-
+                  
                   function showStats()
                   {
                      getStatData(function(data)
@@ -208,35 +208,33 @@
                         $('#cur_num_viewers').html('').text(data.viewers);
                      });
                   }
-
-						// refresh the stream status + viewers
+                  
+                  // refresh the stream status + viewers
                   sinterval = setInterval(function()
                   {
                      showStats();
                   }, 10000);
-
+                  
                   showStats();
-
-
+                  
                   $('#editserver').append(
                      $('<button>').attr('class', 'floatright').click(function()
                      {
                         var host = $('#config-host').val();
                         var name = $('#config-name').val();
-
+                        
                         settings.settings.config.host = host;
                         settings.settings.config.name = name;
-
+                        
                         loadSettings(function()
                         {
                            showTab('overview');
                         });
                      }).text( 'save' )
                   );
-
-
+                  
                   var forcesave = $('<div>').attr('id', 'forcesave');
-
+                  
                   forcesave.append(
                      $('<p>').text('Click the button below to force an immediate settings save. This differs from a regular save to memory and file save on exit by saving directly to file while operating. This may slow server processes for a short period of time.')
                   ).append(
@@ -248,325 +246,339 @@
                        }
                      }).text( 'force save to JSON file' )
                   );
-
+                  
                   $('#page').append(forcesave);
-
+                  
                   break;
-
-
-
-
+                  
+                  
                case 'protocols':
-
+                  
                   $table = $('<table>');
-                  $table.html("<thead><th>Protocol</th><th>Settings</th><th></th></thead>");
+                  $table.html("<thead><th>Protocol</th><th>Status</th><th>Settings</th><th></th></thead>");
                   $tbody = $('<tbody>');
-
+                  
                   var tr, i, protocol,
-                      len = (settings.settings.config.protocols ? settings.settings.config.protocols.length : 0);
-
+                     len = (settings.settings.config.protocols ? settings.settings.config.protocols.length : 0);
+                  
                   $tbody.html('');
-
+                  pids = [];
+                  
                   for(i = 0; i < len; i++)
                   {
                      protocol = settings.settings.config.protocols[i];  // local copy
-
+                     pids.push(i);
+                     
                      tr = $('<tr>').attr('id', 'protocol-' + i);
-
+                     
                      tr.append( $('<td>').text( protocol.connector ) );
-					 
-					 s = "";
-					 for (option in protocol) {
-						if ((option != 'connector') && (option != 'online')) {
-							u = 
-							s += option+': '+((protocol[option] == '') ? 'default' : protocol[option] )+', ';
-						}
-					 }
-					 s = s.slice(0,-2);
-					 
-					 tr.append( $('<td>').text( s ) ); 
-
+                     
+                     tr.append( $('<td>').html( formatStatus( protocol.online ) ) );
+                     
+                     s = "";
+                     for (option in protocol) 
+                     {
+                        if ((option != 'connector') && (option != 'online')) 
+                        {
+                           s += option+': '+((protocol[option] == '') ||  (protocol[option] == 0) ? 'default' : protocol[option] )+', ';
+                        }
+                     }
+                     s = s.slice(0,-2);
+                       
+                     tr.append( $('<td>').text( s ) ); 
+                     
                      tr.append( $('<td>').attr('class', 'center').append( $('<button>').text('edit').click(function()
-						 {
-							id = $(this).parent().parent().attr('id').replace('protocol-', '');
-							showTab('editprotocol', id);
-						 }) ).append( $('<button>').click(function()
-		                 {
-		                    if(confirmDelete('Are you sure you want to delete this protocol?') == true)
-		                    {
-		                       var id = $(this).parent().parent().attr('id').replace('protocol-', '');
-		                       settings.settings.config.protocols.splice(id, 1);
-		                       $(this).parent().parent().remove();
-		                       loadSettings();
-		                    }
-		                 }).text('delete') ) );
-
+                     {
+                        id = $(this).parent().parent().attr('id').replace('protocol-', '');
+                        showTab('editprotocol', id);
+                     }) ).append( $('<button>').click(function()
+                     {
+                        if(confirmDelete('Are you sure you want to delete this protocol?') == true)
+                        {
+                           var id = Number($(this).parent().parent().attr('id').replace('protocol-', ''));
+                           var pid = pids.indexOf(id);
+                           settings.settings.config.protocols.splice(pid, 1);
+                           $(this).parent().parent().remove();
+                           loadSettings();
+                        }
+                     }).text('delete') ) );
+                     
                      $tbody.append(tr);
                   }
-
+                     
                   $table.append($tbody);
                   $('#page').append($table);
-				  
-				  $('#page').append(
+                      
+                  $('#page').append(
                      $('<button>').attr('class', 'floatright').click(function()
                      {
                         showTab('editprotocol', 'new');
                      }).text('add new')
                   );
-
+                  
+                  function refreshProtocolStatus() 
+                  {
+                     getData(function(data)
+                     {
+                        protocol = data.config.protocols;
+                        for (index in protocol) 
+                        {
+                           var row = $('#protocol-' + index);
+                           var status = protocol[index].online;
+                          
+                           $(row.children()[1]).html( formatStatus(status) );
+                           
+                           if (status == undefined) 
+                           {
+                              setTimeout(function()
+                              {
+                                 refreshProtocolStatus();
+                              },1000);
+                           }
+                        }
+                     });
+                  }
+                  
+                  pinterval = setInterval(function()
+                  {
+                     refreshProtocolStatus();
+                  },10000);
+                  refreshProtocolStatus();
+                  
                   break;
-
-
-
-
-
-
-
+                  
                case 'editprotocol':
-				   if (streamname != 'new') { currentdata = settings.settings.config.protocols[streamname]; }
-				   
-				   currentconnectors = [];
-				   for (var index in settings.settings.config.protocols) { //build a list of the current connectors to see if the dependencies are already configured
-						currentconnectors.push(settings.settings.config.protocols[index].connector);
-				   }
-				   
-				   function buildProtocolFields(selectedProtocol) 
-				   {
-						 data = settings.settings.capabilities.connectors[selectedProtocol];
-						 
-						 $t = $('<p>').text(data.desc);
-						 if ((typeof data.deps != 'undefined') && (data.deps))
-					  	 {
-							$t.append($('<p>').text('Dependencies:'));
-							$s = $('<ul>');
-							deps = data.deps.split(',');
-							for (var index in deps) 
-							{
-								t = deps[index];
-								if ($.inArray(deps[index],currentconnectors) < 0) {
-									$u = $('<span>').text(' (Not yet configured!)').addClass('red');
-								}
-								else {
-									$u = $('<span>').text(' (Configured)').addClass('green');
-								}
-								$s.append($('<li>').text(t).append($u));
-							}
-							$t.append($s);
-						 }						 
-						 $('#protocoldesc').html( $t );
-						 
-						 $protocolfields = $('<div>');
-						 if (typeof data.required != 'undefined') 
-						 {
-							 $protocolfields.append( $('<p>').text('Required parameters') );
-							 for(fieldname in data.required)
-							 {
-								switch (data.required[fieldname].type) 
-								{
-									case 'str':
-										var inputType = 'text'
-										break;
-									case 'uint':
-										var inputType = 'number'
-										var func = 'uint'
-										break;
-									case 'int':
-										var inputType = 'number'
-										break;
-									default:
-										
-										break;
-								}
-								$i = $('<input>').attr('type',inputType).attr('id','protocol-parameter-'+fieldname).addClass('required');
-								if (func == 'uint') {
-									$i.addClass('uint');
-								}
-								$protocolfields.append(
-									$('<label>').text(data.required[fieldname].name).attr('title',data.required[fieldname].help).append($i)
-								);
-							 }
-						 }
-						 if (typeof data.optional != 'undefined') 
-						 {
-							 $protocolfields.append( $('<p>').text('Optional parameters') );
-							 for(fieldname in data.optional)
-							 {
-								switch (data.optional[fieldname].type) 
-								{
-									case 'str':
-										var inputType = 'text'
-										break;
-									case 'uint':
-										var inputType = 'number'
-										var func = 'uint'
-										break;
-									case 'int':
-										var inputType = 'number'
-										break;
-									default:
-										
-										break;
-								}
-								$i = $('<input>').attr('type',inputType).attr('id','protocol-parameter-'+fieldname);
-								if (func == 'uint') {
-									$i.addClass('uint');
-								}
-								$protocolfields.append(
-									$('<label>').text(data.optional[fieldname].name).attr('title',data.optional[fieldname].help).append($i)
-								);
-							 }
-						 }
-						 $('#protocolfields').html($protocolfields);
-						 if (streamname != 'new') {
-							for (fieldname in currentdata) {
-								if ((fieldname != 'connector') && (fieldname != 'online')) { 
-									$('#protocol-parameter-'+fieldname).val(currentdata[fieldname]);
-								}								
-							}
-						 }
-				   }
-				   
-				   loadSettings(function()
-				   {					  
-					  if (streamname == 'new') { t = 'add new protocol'; }
-					  else { t = 'edit protocol'; }
-					  
-					  $('#page').append( $('<p>').text(t) );
-					  
-					  $selectprotocol = $('<select>').attr('id', 'edit-protocol').change(function()
-					  {
-						 buildProtocolFields($(this).children(':selected').val());						 
-					  });
-					  for(protocol in settings.settings.capabilities.connectors)
-					  {
-						 if ((streamname != 'new') && (currentdata.connector == protocol)) {
-							 $selectprotocol.append(
-								$('<option>').attr('value', protocol).attr('selected','selected').text(protocol)
-							 );
-						 }
-						 else {
-							 $selectprotocol.append(
-								$('<option>').attr('value', protocol).text(protocol)
-							 );
-						 }
-					  }
-					 
-					  $div = $('<div>').attr('id', 'editprotocol');
-					  $div.append( 
-					     $('<label>').attr('for', 'protocol-edit-protocol').text('protocol').append(
-						    $selectprotocol
-						 )
-					  );		  
-	
-					  	
-					  $('#page').append( $div );
-					  $('#editprotocol').append( $('<div>').attr('id','protocoldesc') );					  
-					  $('#editprotocol').append( $('<div>').attr('id', 'protocolfields') );
-					  $('#editprotocol').append(
-						$('<button>').text('cancel').addClass('floatright').click(function()
-						{
-							showTab('protocols');
-						})
-					  );
-					  $('#editprotocol').append(
-						$('<button>').text('save').addClass('floatright').click(function()
-						{
-							error = false;
-							$('input.required').each(function(){ //check if all required fields have contents
-								if ($(this).val() == '') {
-									$(this).focus();
-									$(this).parent().addClass('red');
-									error = true;
-								}
-							});
-							$('input[type="number"]').each(function(){ //turn all numbers into integers
-								$(this).val(Math.floor($(this).val()));
-							});
-							$('input.uint').each(function(){ //check if all uints are actually uints
-								if ($(this).val() < 0) {
-									$(this).focus();
-									$(this).parent().addClass('red');
-									error = true;
-								}
-							});
-						   if (error) { return; }
+                  if (streamname != 'new') 
+                  { 
+                     currentdata = settings.settings.config.protocols[streamname]; 
+                  }
+                  
+                  currentconnectors = [];
+                  // build a list of the current connectors to see if the dependencies are already configured
+                  for (var index in settings.settings.config.protocols) 
+                  { 
+                     currentconnectors.push(settings.settings.config.protocols[index].connector);
+                  }
+                  
+                  function buildProtocolParameterFields(data,required)
+                  {
+                     for (fieldname in data)
+                     {
+                        switch(data[fieldname].type)
+                        {
+                           case 'str':
+                              var inputType = 'text'
+                              break;
+                           case 'uint':
+                              var inputType = 'number'
+                              var func = 'uint'
+                              break;
+                           case 'int':
+                              var inputType = 'number'
+                              break;
+                        }
+                        $i = $('<input>').attr('type',inputType).attr('id','protocol-parameter-'+fieldname);
+                        if (func == 'uint') 
+                        {
+                           $i.addClass('uint');
+                        }
+                        if (required) 
+                        {
+                           $i.addClass('required');
+                        }
+                        $protocolfields.append(
+                           $('<label>').text(data[fieldname].name).attr('title',data[fieldname].help).append($i)
+                        );
+                     }
+                  }
+                  
+                  function buildProtocolFields(selectedProtocol) 
+                  {
+                     data = settings.settings.capabilities.connectors[selectedProtocol];
+                     
+                     $t = $('<p>').text(data.desc);
+                     if ((typeof data.deps != 'undefined') && (data.deps))
+                     {
+                        $t.append($('<p>').text('Dependencies:'));
+                        $s = $('<ul>');
+                        deps = data.deps.split(',');
+                        for (var index in deps) 
+                        {
+                           t = deps[index];
+                           if ($.inArray(deps[index],currentconnectors) < 0) 
+                           {
+                              $u = $('<span>').text(' (Not yet configured!)').addClass('red');
+                           }else{
+                              $u = $('<span>').text(' (Configured)').addClass('green');
+                           }
+                           $s.append($('<li>').text(t).append($u));
+                        }
+                        $t.append($s);
+                     }						 
+                     $('#protocoldesc').html( $t );
+                     
+                     $protocolfields = $('<div>');
+                     if (typeof data.required != 'undefined') 
+                     {
+                        $protocolfields.append( $('<p>').text('Required parameters') );
+                        buildProtocolParameterFields(data.required,true);
+                     }
+                     if (typeof data.optional != 'undefined') 
+                     {
+                        $protocolfields.append( $('<p>').text('Optional parameters') );
+                        buildProtocolParameterFields(data.optional,false);
+                     }
+                     $('#protocolfields').html($protocolfields);
+                     if (streamname != 'new') 
+                     {
+                        for (fieldname in currentdata) 
+                        {
+                           if ((fieldname != 'connector') && (fieldname != 'online')) 
+                           { 
+                              $('#protocol-parameter-'+fieldname).val(currentdata[fieldname]);
+                           }								
+                        }
+                     }
+                  }
+                     
+                  loadSettings(function()
+                  {					  
+                     if (streamname == 'new') { t = 'add new protocol'; }
+                     else { t = 'edit protocol'; }
+                     
+                     $('#page').append( $('<p>').text(t) );
+                     
+                     $selectprotocol = $('<select>').attr('id', 'edit-protocol').change(function()
+                     {
+                        buildProtocolFields($(this).children(':selected').val());						 
+                     });
+                     for(protocol in settings.settings.capabilities.connectors)
+                     {
+                        if ((streamname != 'new') && (currentdata.connector == protocol)) {
+                           $selectprotocol.append(
+                              $('<option>').attr('value', protocol).attr('selected','selected').text(protocol)
+                           );
+                        }else{
+                           $selectprotocol.append(
+                              $('<option>').attr('value', protocol).text(protocol)
+                         );
+                        }
+                     }
+                     
+                     $div = $('<div>').attr('id', 'editprotocol');
+                     $div.append( 
+                        $('<label>').attr('for', 'protocol-edit-protocol').text('protocol').append(
+                           $selectprotocol
+                        )
+                     );		
+                     
+                     $('#page').append( $div );
+                     $('#editprotocol').append( $('<div>').attr('id','protocoldesc') );					  
+                     $('#editprotocol').append( $('<div>').attr('id', 'protocolfields') );
+                     $('#editprotocol').append(
+                        $('<button>').text('cancel').addClass('floatright').click(function()
+                        {
+                           showTab('protocols');
+                        })
+                     );
+                     $('#editprotocol').append(
+                        $('<button>').text('save').addClass('floatright').click(function()
+                        {
+                           error = false;
+                           //check if all required fields have contents
+                           $('input.required').each(function()
+                           { 
+                              if ($(this).val() == '') 
+                              {
+                                 $(this).focus();
+                                 $(this).parent().addClass('red');
+                                 error = true;
+                              }
+                           });
+                           //turn all numbers into integers
+                           $('input[type="number"]').each(function()
+                           { 
+                              $(this).val(Math.floor($(this).val()));
+                           });
+                           //check if all uints are actually uints
+                           $('input.uint').each(function()
+                           { 
+                              if ($(this).val() < 0) 
+                              {
+                                 $(this).focus();
+                                 $(this).parent().addClass('red');
+                                 error = true;
+                              }
+                           });
+                           if (error) { return; }
                            if(!settings.settings.config.protocols)
                            {
                               settings.settings.config.protocols = [];
                            }
-						   
-						   connectorval = $('#edit-protocol').val()
+                           
+                           connectorval = $('#edit-protocol').val()
                            var newprotocol =
                            {
-								connector: connectorval
+                              connector: connectorval
                            };
-						   $('input').each(function(){
-								newprotocol[$(this).attr('id').split('-')[2]] = $(this).val();;
-						   });
-						   if (streamname == 'new') {
-								settings.settings.config.protocols.push(newprotocol);
-						   }
-						   else {
-								settings.settings.config.protocols[streamname] = newprotocol;
-						   }
-
+                           
+                           $('input').each(function(){
+                              newprotocol[$(this).attr('id').split('-')[2]] = $(this).val();;
+                           });
+                           if (streamname == 'new') {
+                              settings.settings.config.protocols.push(newprotocol);
+                           }else{
+                              settings.settings.config.protocols[streamname] = newprotocol;
+                           }
+                           
                            loadSettings(function()
                            {
                               showTab('protocols');
                            });
-						})
-					  );
-
-				  buildProtocolFields($('select#edit-protocol :selected').val());
-
-				  });
-
-			   
-			   
-			   
-			      break;
-			   
-			   
-			   
-			   
-			   
-			   
-			   
-			   case 'streams':
-
+                        })
+                     );
+                     
+                     buildProtocolFields($('select#edit-protocol :selected').val());
+                     
+                  });
+                  
+                  break;
+                  
+               case 'streams':
+                  
                   // the filter element containr
                   $div = $('<div>').attr('id', 'streams-filter');
-
-						// filters the table. uses the streamsdisplay
+                  
+                  // filters the table. uses the streamsdisplay
                   function filterTable()
                   {
                      $('#streams-list-tbody').children().each(function(k, v)
                      {
                         var type = $($(v).children()[0]).text().toLowerCase();
-
+                        
                         $(v).show();
-
+                        
                         if(type == 'recorded' && streamsdisplay[0] == false)
                         {
                            $(v).hide();
                         }
-
+                        
                         if(type == 'live' && streamsdisplay[1] == false)
                         {
                            $(v).hide();
                         }
                      });
                   }
-
+                  
                   function filterOn(event, elem)
                   {
                      if(event.target.id == '')
                      {
                         return;  // label click goes bubbles on checkbox, so ignore it
                      }
-
+                     
                      var what = $(elem).text();
-
+                     
                      if(what == 'recorded')
                      {
                         streamsdisplay[0] = !streamsdisplay[0];
@@ -575,84 +587,84 @@
                         streamsdisplay[1] = !streamsdisplay[1];
                         $('#stream-filter-live').attr('checked', streamsdisplay[1]);
                      }
-
+                     
                      filterTable();
                   }
-
                   $div.append(
                      $('<label>').attr('for', 'stream-filter-recorded').text('recorded').append(
                         $('<input>').attr('type', 'checkbox').attr('id', 'stream-filter-recorded').attr('checked', streamsdisplay[0])
-                     ).click(function(event)
-                     {
-                        filterOn(event, this);
-                     })
+                           ).click(function(event)
+                           {
+                              filterOn(event, this);
+                           })
                   );
                   $div.append(
                      $('<label>').attr('for', 'stream-filter-live').text('live').append(
                         $('<input>').attr('type', 'checkbox').attr('id', 'stream-filter-live').attr('checked', streamsdisplay[1])
-                     ).click(function(event)
-                     {
-                        filterOn(event, this);
-                     })
+                           ).click(function(event)
+                           {
+                              filterOn(event, this);
+                           })
                   );
-
                   $('#page').append($div);
-
-						// refresh every streams' data (status and viewer count)
-						function refreshStreams()
-						{
-							getStreamsData(function(streams)
-							{
-								for(stream in streams)
-								{
-									if( $('stream-' + stream) )
-									{
-										var row = $('#stream-' + stream);
-										var status = streams[stream][0];
-
-										$(row.children()[3]).html( formatStatus(status) );
-
-										$(row.children()[4]).text(streams[stream][1]);
-									}
-								}
-							});
-						};
-
-						sinterval = setInterval(function()
-						{
-							refreshStreams();
-						}, 10000);
-
-						refreshStreams();
-
+                  
+                  // refresh every streams' data (status and viewer count)
+                  function refreshStreams()
+                  {
+                    getStreamsData(function(streams)
+                    {
+                      for(stream in streams)
+                      {
+                        if( $('stream-' + stream) )
+                        {
+                          var row = $('#stream-' + stream);
+                          var status = streams[stream][0];
+                          
+                          $(row.children()[4]).html( formatStatus(status) );
+                          
+                          $(row.children()[5]).text(streams[stream][1]);
+                        }
+                      }
+                    });
+                  };
+                  
+                  sinterval = setInterval(function()
+                  {
+                    refreshStreams();
+                  }, 10000);
+                  
+                  refreshStreams();
+                  
                   $table = $('<table>');
-                  $table.html("<thead><th>Type</th><th>Embed</th><th>Name</th><th>Status</th><th>Viewers</th><th>Edit</th></thead>");
+                  $table.html("<thead><th class=sort-type-int>Id</th><th class=sort-type-string>Type</th><th>Embed</th><th class='sort-type-string sortdesc'>Name</th><th class=sort-type-string>Status</th><th class=sort-type-int>Viewers</th><th>Edit</th></thead>");
                   $tbody = $('<tbody>');
-
+                        
                   var stream, cstr, $tr;
-
+                  
                   $tbody.html('').attr('id', 'streams-list-tbody');
-
+                  
                   for(stream in settings.settings.streams)
                   {
                      var cstr = settings.settings.streams[stream];
-
+                     
                      $tr = $('<tr>').attr('id', 'stream-' + stream);
-
+                     
+                     $tr.append( $('<td>').text( cstr.sid ) );
+                     
                      $tr.append( $('<td>').text( TypeofResource( cstr.channel.URL ) ) );
-
+                     
                      $tr.append( $('<td>').append( $('<button>').text('embed').click(function()
                      {
                         var sname = $(this).parent().parent().attr('id').replace('stream-', '');
                         showTab('embed', sname);
                      }) ) );   // end function, end click(), end append(), end append(). Huzzah jQuery.
-
-                     $tr.append( $('<td>').text(cstr.name) );
-
-							$tr.append( $('<td>').html( formatStatus( cstr.online ) ) );
-
+                     
+                     $tr.append( $('<td>').text( cstr.name ) );
+                     
+                     $tr.append( $('<td>').html( formatStatus( cstr.online ) ) );
+                     
                      var cviewers = 0;
-
+                     
                      if(settings.settings.statistics && settings.settings.statistics[stream])
                      {
                         if(settings.settings.statistics[stream] && settings.settings.statistics[stream].curr)
@@ -665,44 +677,48 @@
                      }else{
                         cviewers = 0;
                      }
-
+                     
                      $tr.append( $('<td>').text( cviewers ) );
-
+                     
                      $tr.append( $('<td>').append( $('<button>').text('edit').click(function()
                      {
                         var sname = $(this).parent().parent().attr('id').replace('stream-', '');
-
+                        
                         showTab('editstream', sname);
                      }) ) );   // end function, end click, end append, end append.
-
+                     
                      $tbody.append($tr);
+                     
+                     //quickly re-check if the streams are online now
+                     if (cstr.online == undefined) 
+                     {
+                        setTimeout(function()
+                        {
+                          refreshStreams();
+                        }, 1000);
+                     }
                   }
-
-                  $table.append($tbody);
+                  
+                  $table.append($tbody).addClass('sortable');
+                  $table.stupidtable();
                   $('#page').append($table);
-
+                  
                   // on page load, also filter with the (users' defined) stream filter
                   filterTable();
-
+                  
                   $('#page').append(
                      $('<button>').attr('class', 'floatright').click(function()
                      {
                         showTab('editstream', 'new');
                      }).text('add new')
                   );
-
+                  
                   break;
-
-
-
-
-
-
-
+                  
                case 'editstream':
-
+                  
                   var sdata, title;
-
+                  
                   if(streamname == 'new')
                   {
                      sdata =
@@ -723,9 +739,9 @@
                      sdata = settings.settings.streams[streamname];
                      title = 'edit stream "' + sdata.name + '"';
                   }
-
+                     
                   $('#page').append( $('<p>').text(title) );
-
+                     
                   $('#page').append(
                      $('<div>').attr('id', 'editserver').append(
                         $('<label>').attr('for', 'stream-edit-name').text('name').append(
@@ -734,19 +750,19 @@
                      ).append(
                         $('<label>').attr('for', 'stream-edit-source').text('source').append(
                            $('<input>').attr('type', 'text').attr('placeholder', 'SOURCE').attr('id', 'stream-edit-source').attr('value', sdata.channel.URL).keyup(function()
-									{
-									   var text = $(this).val();
-
-										if(text.charAt(0) == '/' || text.substr(0, 7) == 'push://')
-										{
-											$('#stream-edit-preset').val('');
-											$('#stream-edit-preset').hide();
-											$('#stream-edit-preset-label').hide();
-										}else{
-											$('#stream-edit-preset').show();
-											$('#stream-edit-preset-label').show();
-										}
-									})
+                           {
+                              var text = $(this).val();
+                     
+                              if(text.charAt(0) == '/' || text.substr(0, 7) == 'push://')
+                              {
+                                 $('#stream-edit-preset').val('');
+                                 $('#stream-edit-preset').hide();
+                                 $('#stream-edit-preset-label').hide();
+                              }else{
+                                 $('#stream-edit-preset').show();
+                                 $('#stream-edit-preset-label').show();
+                              }
+                           })
                         )
                      ).append(
                         $('<label>').attr('id', 'stream-edit-preset-label').attr('for', 'stream-edit-preset').text('preset').append(
@@ -754,20 +770,19 @@
                         )
                      )
                   );
-
-						// if the source is push or file, don't do a preset
-					   var text = $('#stream-edit-source').val();
-
-						if(text.charAt(0) == '/' || text.substr(0, 7) == 'push://')
-						{
-							$('#stream-edit-preset').hide();
-							$('#stream-edit-preset-label').hide();
-						}else{
-							$('#stream-edit-preset').show();
-							$('#stream-edit-preset-label').show();
-						}
-
-
+                  
+                  // if the source is push or file, don't do a preset
+                  var text = $('#stream-edit-source').val();
+                  
+                  if(text.charAt(0) == '/' || text.substr(0, 7) == 'push://')
+                  {
+                     $('#stream-edit-preset').hide();
+                     $('#stream-edit-preset-label').hide();
+                  }else{
+                     $('#stream-edit-preset').show();
+                     $('#stream-edit-preset-label').show();
+                  }
+                  
                   $('#editserver').append(
                      $('<button>').attr('class', 'floatright').click(function()
                      {
@@ -786,37 +801,47 @@
                         }
                      }).text( streamname == 'new' ? 'cancel' : 'delete' )
                   );
-
+                  
                   $('#editserver').append(
                      $('<button>').attr('class', 'floatright').click(function()
                      {
                         var n = $('#stream-edit-name');
                         var s = $('#stream-edit-source');
                         var p = $('#stream-edit-preset');
-
+                        
                         if(n.val() == ''){ n.focus(); return; }
                         if(s.val() == ''){ s.focus(); return; }
-
-								var newname = n.val().replace(/([^a-zA-Z0-9_])/g, '').toLowerCase();
-
+                        
+                        var newname = n.val().replace(/([^a-zA-Z0-9_])/g, '').toLowerCase();
+                        
                         sdata.name = newname;
                         sdata.channel.URL = s.val();
                         sdata.preset.cmd = p.val();
-
+                        
                         if(streamname == 'new')
                         {
                            streamname = newname;
+                           sdata.sid = 0;
+                           for (strm in settings.settings.streams)
+                           {
+                              sdata.sid = Math.max(sdata.sid,settings.settings.streams[strm].sid); 
+                           }
+                           sdata.sid += 1;
                         }
-
+                        else 
+                        {
+                           sdata.sid = settings.settings.streams[streamname].sid;
+                        }
+                        
                         if(!settings.settings.streams)
                         {
                            settings.settings.streams = {};
                         }
-
-								delete settings.settings.streams[streamname];
-
+                        
+                        delete settings.settings.streams[streamname];
+                        
                         settings.settings.streams[newname] = sdata;
-
+                        
                         loadSettings(function()
                         {
                            showTab('streams');
@@ -824,128 +849,117 @@
                            
                      }).text('save')
                   );
-
+                  
                   break;
-
-
-
-
-
+                  
                case 'embed':
-
-						if(isThereAHTTPConnector())
-						{
-							var embedbase = 'http://' + parseURL(settings.server).host + ':' + getHTTPControllerPort() + '/';
-
-							$('#page').append( $('<p>').attr('class', 'nocapitals').text('The info embed URL is "' + embedbase + 'info_' + streamname + '.js".') );
-							$('#page').append( $('<p>').attr('class', 'nocapitals').text('The embed embed URL is "' + embedbase + 'embed_' + streamname + '.js".') );
-
-							$('#page').append( $('<button>').text('preview').click(function()
-							{
-								showTab('preview', streamname);
-							} ) );
-
-						}else{
-							$('#page').append( $('<p>').attr('class', 'nocapitals').text('Could\'t find a HTTP connector. Please add a HTTP connector on the "protocol" page.') );
-						}
-
+                  
+                  if(isThereAHTTPConnector())
+                  {
+                     var embedbase = 'http://' + parseURL(settings.server).host + ':' + getHTTPControllerPort() + '/';
+                     
+                     $('#page').append( $('<p>').attr('class', 'nocapitals').text('The info embed URL is "' + embedbase + 'info_' + streamname + '.js".') );
+                     $('#page').append( $('<p>').attr('class', 'nocapitals').text('The embed embed URL is "' + embedbase + 'embed_' + streamname + '.js".') );
+                     
+                     $('#page').append( $('<button>').text('preview').click(function()
+                     {
+                        showTab('preview', streamname);
+                     } ) );
+                     
+                  }else{
+                     $('#page').append( $('<p>').attr('class', 'nocapitals').text('Could\'t find a HTTP connector. Please add a HTTP connector on the "protocol" page.') );
+                  }
+                  
                   break;
-
-
-
+                  
                case 'preview':
-
-						var embed = 'http://' + parseURL(settings.server).host + ':' + getHTTPControllerPort() + '/embed_' + streamname + '.js';
-
-						$('#page').append( $('<div>').attr('id', 'previewcontainer') );
-
-						// jQuery doesn't work -> use DOM magic
-						var script = document.createElement('script');
-						script.src = embed;
-						document.getElementById('previewcontainer').appendChild( script );
-
+                  
+                  var embed = 'http://' + parseURL(settings.server).host + ':' + getHTTPControllerPort() + '/embed_' + streamname + '.js';
+                  
+                  $('#page').append( $('<div>').attr('id', 'previewcontainer') );
+                  
+                  // jQuery doesn't work -> use DOM magic
+                  var script = document.createElement('script');
+                  script.src = embed;
+                  document.getElementById('previewcontainer').appendChild( script );
+                  
                   break;
-
-
-
-
-
+                  
                case 'limits':
                   $table = $('<table>');
                   $table.html("<thead><th>Type</th><th>Hard/soft</th><th>Value</th><th>applies to</th><th>Action</th></thead>");
                   $tbody = $('<tbody>');
-
+                  
                   var i, tr, limit, stream, clims,
                       alllimits = settings.settings.config.limits;
-
+                      
                   for(stream in settings.settings.streams)
                   {
                      clims = settings.settings.streams[stream].limits;
-
+                     
                      $.each(clims, function(k, v)
                      {
                         this.appliesto = stream;
                         this.appliesi = k;
                      });
-
+                     
                      alllimits = alllimits.concat(clims);
                   }
-
+                  
                   len = alllimits.length;
-
+                  
                   // remove old items
                   $tbody.html('');
-
+                  
                   for(i = 0; i < len; i++)
                   {
                      tr = $('<tr>').attr('id', 'limits-' + i);
                      limit = alllimits[i];
-
+                     
                      tr.append( $('<td>').text( shortToLongLimit(limit.name) ) );
                      tr.append( $('<td>').text( limit.type ) );
                      tr.append( $('<td>').text( limit.val ) );
-
-
+                     
                      if(limit.appliesto)
                      {
                         tr.append( $('<td>').text( settings.settings.streams[limit.appliesto].name ).attr('id', 'limit-at-' + limit.appliesto + '-' + limit.appliesi) );
                      }else{
                         tr.append( $('<td>').text( 'server' ) );
                      }
-
+                     
                      delete limit.appliesto;
                      delete limit.appliesi;
-
+                     
                      tr.append( $('<td>').attr('class', 'center').append( $('<button>').click(function()
-                                {
-                                   if(confirmDelete('Are you sure you want to delete this limit?') == true)
-                                   {
-                                      var id = $(this).parent().parent().attr('id').replace('limits-', '');
-                                      var at = $($(this).parent().parent().children()[3]).attr('id');
-
-                                      if(at == undefined)
-                                      {
-                                         settings.settings.config.limits.splice(id, 1);
-                                      }else{
-                                         var data = at.replace('limit-at-', '').split('-');
-                                         var loc = data.pop();
-                                         data = data.join('-');
-
-                                         settings.settings.streams[data].limits.splice(loc, 1);
-                                      }
-
-                                      $(this).parent().parent().remove();
-
-                                      loadSettings();
-                                   }
-                                }).text('delete') ) );
-
+                     {
+                       if(confirmDelete('Are you sure you want to delete this limit?') == true)
+                       {
+                          var id = $(this).parent().parent().attr('id').replace('limits-', '');
+                          var at = $($(this).parent().parent().children()[3]).attr('id');
+                          
+                          if(at == undefined)
+                          {
+                             settings.settings.config.limits.splice(id, 1);
+                          }else{
+                             var data = at.replace('limit-at-', '').split('-');
+                             var loc = data.pop();
+                             data = data.join('-');
+                             
+                             settings.settings.streams[data].limits.splice(loc, 1);
+                          }
+                          
+                          $(this).parent().parent().remove();
+                          
+                          loadSettings();
+                       }
+                     }).text('delete') ) );
+                                
                      $tbody.append(tr);
                   }
-
+                  
                   // add new limit
                   $nltr = $('<tr>').attr('class', 'outsidetable');
-
+                  
                   // type selector
                   $ltype = $('<select>').attr('id', 'new-limit-type');
                   for(i = 0; i < ltypes.length; i++)
@@ -957,10 +971,10 @@
                   $nltr.append( $('<td>').append( $('<select>').attr('id', 'new-limit-hs').append( $('<option>').attr('value', 'hard').text('Hard limit') ).append( $('<option>').attr('value', 'soft').text('Soft limit') ) ) );
                   // value
                   $nltr.append( $('<td>').append( $('<input>').attr('type', 'text').attr('id', 'new-limit-val') ) );
-
+                  
                   // applies to (stream)
                   var $appliesto = $('<select>').attr('id', 'new-limit-appliesto').append( $('<option>').attr('value', 'server').text('Server') );
-
+                  
                   for(var strm in settings.settings.streams)
                   {
                      $appliesto.append(
@@ -968,7 +982,7 @@
                      );
                   }
                   $nltr.append( $('<td>').append( $appliesto ) );
-
+                  
                   $nltr.append(
                      $('<td>').attr('class', 'center').append(
                         $('<button>').click(function()
@@ -979,55 +993,53 @@
                               type: $('#new-limit-hs :selected').val(),
                               val:  $('#new-limit-val').val()
                            };
-
+                           
                            if( $('#new-limit-appliesto').val() == 'server')
                            {
                               settings.settings.config.limits.push(obj);
                            }else{
                               settings.settings.streams[ $('#new-limit-appliesto').val() ].limits.push(obj);
                            }
-
+                           
                            loadSettings(function()
                            {
                               showTab('limits');
                            });
-
+                           
                         }).text('add new')
                      )
                   );
                   $tbody.append($nltr);
-
+                  
                   $table.append($tbody);
                   $('#page').append($table);
-
+                  
                   break;
-
-
-
+                  
                case 'logs':
                   $table = $('<table>');
                   $table.html("<thead><th>Date<span class='theadinfo'>(MM/DD/YYYY)</span></th><th>Type</th><th>Message</th></thead>");
                   $tbody = $('<tbody>');
-
-						if(!settings.settings.log)
-						{
-							return;  // no logs, so just bail
-						}
+                  
+                  if(!settings.settings.log)
+                  {
+                     return;  // no logs, so just bail
+                  }
                   var i, cur, $tr,
                       logs = settings.settings.log,
                       len = logs.length;
-
+                      
                   if(len >= 2 && settings.settings.log[0][0] < settings.settings.log[len - 1][0])
                   {
                      logs.reverse();
                   }
-
+                  
                   $tbody.html('');
-
+                  
                   for(i = 0; i < len; i++)
                   {
                      cur = settings.settings.log[i];
-
+                     
                      $tr = $('<tr>').append(
                         $('<td>').text(formatDate(cur[0]))
                      ).append(
@@ -1035,13 +1047,13 @@
                      ).append(
                         $('<td>').text(cur[2])
                      );
-
+                     
                      $tbody.append($tr);
                   }
-
+                  
                   $table.append($tbody);
                   $('#page').append($table);
-
+                  
                   $('#page').append(
                      $('<button>').attr('class', 'floatright').click(function()
                      {
@@ -1052,16 +1064,14 @@
                         });
                      }).text('Purge logs')
                   );
-
+                  
                   break;
-
-
-
+                  
                case 'disconnect':
                   showTab('login');
                   setHeaderState('disconnected');
                   $('#nav').css('visibility', 'hidden');
-
+                  
                   settings =
                   {
                      server: '',
@@ -1074,12 +1084,10 @@
                      settings: {}
                   };
                   break;
-
             }  // end switch
-
-
+            
             //placeholder for older browsers
             $('input[placeholder]').placeholder();
-
+            
          }
 
diff --git a/lsp/server.html b/lsp/server.html
index d087d8fb..b6fe2a3c 100644
--- a/lsp/server.html
+++ b/lsp/server.html
@@ -13,6 +13,7 @@
       <script src='md5.js'></script>
       <script src='main.js'></script>
       <script src='functions.js'></script>
+      <script src='tablesort.js'></script>
 
       <link rel='stylesheet' href='style.css' />
 
diff --git a/lsp/style.css b/lsp/style.css
index 32c4d1e7..7aba5a72 100644
--- a/lsp/style.css
+++ b/lsp/style.css
@@ -117,6 +117,7 @@ button
    background-color: #505050;
    color: #fff;
    border: 0;
+   margin: 0 2px 0 2px;
 }
 
 
@@ -345,6 +346,20 @@ td
    width: auto;
    padding: 0;
 }
+.sortable th.sort-type-string, .sortable th.sort-type-int, .sortable th.sort-type-float
+{
+   background: transparent url('graphics/sort.png') no-repeat 5px 50%;
+}
+
+.sortable th.sortasc
+{
+   background-image: url('graphics/sort_asc.png');
+}
+
+.sortable th.sortdesc
+{
+   background-image: url('graphics/sort_desc.png');
+}
 #protocoldesc p
 {
    font-weight: normal;
@@ -352,7 +367,7 @@ td
 }
 #protocolfields 
 {
-	margin-top: 10px;
+   margin-top: 10px;
 }
 
 #forcesave
diff --git a/lsp/tablesort.js b/lsp/tablesort.js
new file mode 100644
index 00000000..16551ff0
--- /dev/null
+++ b/lsp/tablesort.js
@@ -0,0 +1,216 @@
+// Stupid jQuery table plugin.
+
+
+
+//http://joequery.github.com/Stupid-Table-Plugin/
+
+
+
+// Call on a table 
+// sortFns: Sort functions for your datatypes.
+(function($){
+  $.fn.stupidtable = function(sortFns){
+    var table = this; sortFns = sortFns || {};
+
+    // ==================================================== //
+    //                  Utility functions                   //
+    // ==================================================== //
+
+    // Merge sort functions with some default sort functions.
+    sortFns = $.extend({}, {
+      "int":function(a,b){ return parseInt(a, 10) - parseInt(b, 10); },
+      "float":function(a,b){ return parseFloat(a) - parseFloat(b); },
+      "string":function(a,b){ if (a<b) return -1; if (a>b) return +1; return 0;}
+    }, sortFns);
+
+    // Array comparison. See http://stackoverflow.com/a/8618383
+    var arrays_equal = function(a,b) { return !!a && !!b && !(a<b || b<a);}
+    
+    // Return the resulting indexes of a sort so we can apply
+    // this result elsewhere. This returns an array of index numbers.
+    // return[0] = x means "arr's 0th element is now at x"
+    var sort_map =  function(arr, sort_function){
+      var sorted = arr.slice(0).sort(sort_function); 
+      var map = [];
+      var index = 0;
+      for(var i=0; i<arr.length; i++){
+        index = $.inArray(arr[i], sorted);
+
+        // If this index is already in the map, look for the next index.
+        // This handles the case of duplicate entries.
+        while($.inArray(index, map) != -1){
+          index++;
+        }
+        map.push(index);
+      }
+      return map;
+    }
+
+    // Apply a sort map to the array. 
+    var apply_sort_map = function(arr, map){
+      var clone = arr.slice(0);
+      for(var i=0; i<map.length; i++){
+        newIndex = map[i];
+        clone[newIndex] = arr[i];
+      }
+      return clone;
+    }
+
+    // Returns true if array is sorted, false otherwise.
+    // Checks for both ascending and descending
+    var is_sorted_array = function(arr, sort_function){
+      var clone = arr.slice(0);
+      var reversed = arr.slice(0).reverse();
+      var sorted = arr.slice(0).sort(sort_function);
+
+      // Check if the array is sorted in either direction.
+      return arrays_equal(clone, sorted) || arrays_equal(reversed, sorted);
+    }
+
+
+   var what_order_sorted = function(data, sf, isa)
+   {
+      var tmp = [data[0], data[data.length - 1]];
+
+      tmp.sort(sf);
+
+      if(data[0] == tmp[0] || !isa)
+      {
+         return 'desc';
+      }else{
+         return 'asc';
+      }
+   }
+
+
+
+    // ==================================================== //
+    //                  Begin execution!                    //
+    // ==================================================== //
+    // Do sorting when THs are clicked
+
+    table.delegate("th", "click", function(){
+
+   if($(this).text().replace(/ /g, '') == '')
+   {
+      // empty header, don't allow sorting
+      return;
+   }
+
+
+      var trs = table.find("tbody tr");
+      var i = $(this).index();
+      var classes = $(this).attr("class");
+      var type = null;
+
+      if (classes){
+        classes = classes.split(/\s+/);
+
+        for(var j=0; j<classes.length; j++){
+          if(classes[j].search("sort-type-") != -1){
+            type = classes[j].replace('sort-', '');
+            break;
+          }
+        }
+        if(type){
+          type = type.split('-')[1];
+        }
+        else{
+          type = "string";
+        }
+      }
+
+
+      // Don't attempt to sort if no data type
+      //if(!type){return false;}
+
+
+      var sortMethod = sortFns[type];
+
+
+      // Gather the elements for this column
+      column = [];
+
+      // Push either the value of the 'data-order-by' attribute if specified
+      // or just the text() value in this column to column[] for comparison.
+      trs.each(function(index,tr){
+        var e = $(tr).children().eq(i);
+        var order_by = e.attr('data-order-by') || e.text();
+	      column.push(order_by);
+      });
+
+
+      // If the column is already sorted, just reverse the order. The sort
+      // map is just reversing the indexes.
+      if(is_sorted_array(column, sortMethod)){
+        column.reverse();
+        var theMap = [];
+        for(var i=column.length-1; i>=0; i--){
+          theMap.push(i);
+        }
+      }else{
+        // Get a sort map and apply to all rows
+        theMap = sort_map(column, sortMethod);
+      }
+
+
+// remove old sort classes (on this and other columns)
+$(this).parent().find('th').each(function()
+{
+   $(this).removeClass('sortasc sortdesc');
+});
+
+// what order are we sorting in?
+var whatorder = what_order_sorted(column, sortMethod, is_sorted_array(column, sortMethod));
+
+// set new sort class
+$(this).addClass(whatorder == 'asc' ? 'sortasc' : 'sortdesc');
+
+
+
+
+
+      var sortedTRs = $(apply_sort_map(trs, theMap));
+
+
+
+      // Replace the content of tbody with the sortedTRs. Strangely (and
+      // conveniently!) enough, .append accomplishes this for us.
+      table.find("tbody").append(sortedTRs);
+
+    });
+
+
+
+// remove th icon if no header text
+   $(this).find('th').each(function()
+   {
+      var hv = $(this).text().replace(/ /g, '');
+
+      if(hv == '')
+      {
+         $(this).css('background', 'transparent');
+      }
+   });
+
+
+
+
+  }
+ })(jQuery);
+
+
+
+
+$('table.sortable').each(function()
+{
+   var rows = $(this).find('tbody tr').length;
+
+   if(rows > 1)
+   {
+      $(this).stupidtable();
+   }else{
+      $(this).removeClass('sortable');
+   }
+});
+