Index: binary-improvements/webserver/js/index.js
===================================================================
--- binary-improvements/webserver/js/index.js	(revision 249)
+++ binary-improvements/webserver/js/index.js	(revision 250)
@@ -1,3 +1,6 @@
-InitializeTabs ();
+//InitializeTabs ();
+var tabs = $("#adminmenu").tabbedContent ({
+	contentdiv: $("#admincontent"),
+});
 SetupInventoryDialog ();
 InitPermissions ();
Index: binary-improvements/webserver/js/inventory_dialog.js
===================================================================
--- binary-improvements/webserver/js/inventory_dialog.js	(revision 249)
+++ binary-improvements/webserver/js/inventory_dialog.js	(revision 250)
@@ -11,12 +11,22 @@
 		var cell = $("#" + containerTypeName + "Field"+cellIdent);
 		var text = $("#" + containerTypeName + "FieldText"+cellIdent);
-		if (itemdata.count > 0) {
+		var qual = $("#" + containerTypeName + "FieldQuality"+cellIdent);
+
+		cell.attr("style", "background-image: none;");
+		cell.removeAttr("title");
+		text.removeClass ("visible");
+		qual.removeClass ("visible");
+
+		if (itemdata !== null) {
 			cell.attr("style", "background-image: url(" + ITEMICONBASEURL + itemdata.name + ".png);");
-			cell.attr("title", itemdata.name);
-			text.text(itemdata.count);
-		} else {
-			cell.attr("style", "background-image: none;");
-			cell.removeAttr("title");
-			text.text("");
+			if (itemdata.quality >= 0) {
+				cell.attr("title", itemdata.name + " (quality: " + itemdata.quality + ")");
+				qual.attr("style", "background-color: #"+ itemdata.qualitycolor);
+				qual.addClass ("visible");
+			} else {
+				cell.attr("title", itemdata.name);
+				text.text(itemdata.count);
+				text.addClass ("visible");
+			}
 		}
 	}
@@ -24,7 +34,7 @@
 	var SetEquipmentItem = function (data, name, cellIdent) {
 		if (data.equipment [name] == false) {
-			SetCellItem ("equipment", cellIdent, { count: 0, name: "" });
+			SetCellItem ("equipment", cellIdent, null);
 		} else {
-			SetCellItem ("equipment", cellIdent, { count: 1, name: data.equipment [name] });
+			SetCellItem ("equipment", cellIdent, data.equipment [name] );
 		}
 	}
@@ -77,4 +87,5 @@
 	var CreateInvCell = function (containerTypeName, cellIdent) {
 		return "<td class=\"invField\" id=\"" + containerTypeName + "Field"+cellIdent+"\">" +
+			"<div class=\"invFieldQuality\" id=\"" + containerTypeName + "FieldQuality" + cellIdent + "\"></div>" +
 			"<span class=\"invFieldText\" id=\"" + containerTypeName + "FieldText"+cellIdent+"\"></span>" +
 			"</td>";
Index: binary-improvements/webserver/js/log.js
===================================================================
--- binary-improvements/webserver/js/log.js	(revision 250)
+++ binary-improvements/webserver/js/log.js	(revision 250)
@@ -0,0 +1,79 @@
+var lastLogLine = -1;
+
+function StartLogModule () {
+	var maxLinesPerRequest = 50;
+
+	var timeout = null;
+	var table = $("#tab_log > table");
+	var lastRead = -1;
+	
+	
+	var updateEvent = function() {
+		$.getJSON( "../api/getlog?firstLine=" + (lastLogLine + 1) + "&lastLine=" + (lastLogLine + maxLinesPerRequest) )
+		.done(function(data) {
+			if (data.firstLine - lastLogLine - 1 > 0) {
+				var row = $("<tr></tr>").appendTo (table);
+				$('<td colspan="4">Missed ' + (data.firstLine - lastLogLine - 1) + ' log entries</td>').addClass ("logcol_missed").appendTo (row);
+			}
+			for (var i = 0; i < data.entries.length; i++) {
+				var row = $("<tr></tr>").addClass (data.entries [i].type).attr ("id", "line" + (data.firstLine + i)).appendTo (table);
+				$("<td>" + data.entries [i].date + " " + data.entries [i].time + "</td>").addClass ("logcol_datetime").appendTo (row);
+				$("<td>" + data.entries [i].uptime + "</td>").addClass ("logcol_uptime").appendTo (row);
+				$("<td>" + data.entries [i].type + "</td>").addClass ("logcol_type").appendTo (row);
+				var msg = $("<td>" + data.entries [i].msg + "</td>").addClass ("logcol_msg").appendTo (row);
+				if (data.entries [i].trace.length > 0) {
+					msg.append ('<br><div class="trace"><span>' + data.entries [i].trace.replace (/\n/g, "</span><span>") + '</span></div><a class="tracebtn"></a>');
+				}
+			}
+
+			if (data.entries.length > 0) {
+				lastLogLine = data.lastLine;
+			}
+		})
+		.fail(function(jqxhr, textStatus, error) {
+			console.log("Error fetching log lines");
+		})
+		.always(function() {
+		});
+		timeout = window.setTimeout(updateEvent, 2000);
+	};
+	
+	var markAsRead = function() {
+		lastRead = lastLogLine;
+		table.find (".readmark").removeClass ("readmark");
+		table.find ("#line" + (lastRead)).addClass ("readmark");
+	};
+	
+	table.on ("click.action", ".tracebtn", function (event) {
+		$(this).toggleClass ("visible");
+		$(this).prev ().toggleClass ("visible");
+	});
+	
+	$(".adminlog #markasread").on ("click.action", null, function (event) {
+		markAsRead ();
+	});
+	
+	
+	tabs.on ("tabbedcontenttabopened", function (event, data) {
+		if (data.newTab === "#tab_log") {
+			updateEvent ();
+
+			markAsRead ();
+			var markedrow = $(".adminlog .readmark");
+			if (markedrow.length > 0) {
+				window.setTimeout (function () {
+					$('html, body').scrollTop (markedrow.offset ().top);
+				}, 20);
+			}
+		} else {
+			window.clearTimeout (timeout);
+		}
+	});
+	
+	if (tabs.tabbedContent ("isTabOpen", "tab_log")) {
+		updateEvent ();
+	}
+
+}
+
+
Index: binary-improvements/webserver/js/map.js
===================================================================
--- binary-improvements/webserver/js/map.js	(revision 249)
+++ binary-improvements/webserver/js/map.js	(revision 250)
@@ -131,7 +131,23 @@
 	});
 
+	var openedPopup = null;
+	var updatingMarkers = false;
+
+	map.on ("popupopen", function (event) {
+		console.log ("open");
+		console.log (event.popup._source);
+		openedPopup = event.popup._source;
+	});
+	map.on ("popupclose", function (event) {
+		if (!updatingMarkers) {
+			console.log ("close");
+			openedPopup = null;
+		}
+	});
+
 	var setPlayerMarkers = function(data) {
 		var online = 0;
 		var offline = 0;
+		updatingMarkers = true;
 		$.each( data, function( key, val ) {
 			var marker;
@@ -147,16 +163,20 @@
 				playersMappingList[val.steamid] = { online: !val.online };
 			}
-			if (playersMappingList[val.steamid].online) {
-				playersOnlineMarkerGroup.removeLayer(marker);
-			} else {
-				playersOfflineMarkerGroup.removeLayer(marker);
-			}
-			marker.setLatLng([val.position.x, val.position.z]);
-			if (val.online) {
-					marker.setOpacity(1.0);
-					playersOnlineMarkerGroup.addLayer(marker);
-			} else {
-					marker.setOpacity(0.5);
-					playersOfflineMarkerGroup.addLayer(marker);
+			
+			oldpos = marker.getLatLng ();
+			if ( playersMappingList[val.steamid].online != val.online || oldpos.lat != val.position.x || oldpos.lng != val.position.z ) {
+				if (playersMappingList[val.steamid].online) {
+					playersOnlineMarkerGroup.removeLayer(marker);
+				} else {
+					playersOfflineMarkerGroup.removeLayer(marker);
+				}
+				marker.setLatLng([val.position.x, val.position.z]);
+				if (val.online) {
+						marker.setOpacity(1.0);
+						playersOnlineMarkerGroup.addLayer(marker);
+				} else {
+						marker.setOpacity(0.5);
+						playersOfflineMarkerGroup.addLayer(marker);
+				}
 			}
 
@@ -169,8 +189,13 @@
 				offline++;
 		});
+		updatingMarkers = false;
+		if (openedPopup != null) {
+			openedPopup.openPopup ();
+		}
 		$( "#mapControlOnlineCount" ).text( online );
 		$( "#mapControlOfflineCount" ).text( offline );
 	}
 
+	var updatePlayerTimeout;
 	var updatePlayerEvent = function() {
 		$.getJSON( "../api/getplayerslocation")
@@ -180,10 +205,22 @@
 		})
 		.always(function() {
-			window.setTimeout(updatePlayerEvent, 2000);
+			updatePlayerTimeout = window.setTimeout(updatePlayerEvent, 4000);
 		});
 	}
 
-	if (HasPermission ("webapi.getplayerslocation")) {
-		window.setTimeout(updatePlayerEvent, 0);
+	tabs.on ("tabbedcontenttabopened", function (event, data) {
+		if (data.newTab === "#tab_map") {
+			if (HasPermission ("webapi.getplayerslocation")) {
+				updatePlayerEvent ();
+			}
+		} else {
+			window.clearTimeout (updatePlayerTimeout);
+		}
+	});
+	
+	if (tabs.tabbedContent ("isTabOpen", "tab_map")) {
+		if (HasPermission ("webapi.getplayerslocation")) {
+			updatePlayerEvent ();
+		}
 	}
 
Index: binary-improvements/webserver/js/permissions.js
===================================================================
--- binary-improvements/webserver/js/permissions.js	(revision 249)
+++ binary-improvements/webserver/js/permissions.js	(revision 250)
@@ -21,13 +21,18 @@
 		}
 		
-		ApplyTabPermissions ();
-
 		if (HasPermission ("web.map")) {
 			StartMapModule ();
 		}		
+		if (HasPermission ("webapi.getlog")) {
+			StartLogModule ();
+		}
 		
-		if (HasPermission ("webapi.getstats")) {
+		if (HasPermission ("webapi.getwebuiupdates")) {
+			StartUIUpdatesModule ();
+		} else if (HasPermission ("webapi.getstats")) {
 			StartStatsModule ();
 		}
+
+		tabs.tabbedContent ("applyPermissions");
 
 	})
Index: binary-improvements/webserver/js/stats.js
===================================================================
--- binary-improvements/webserver/js/stats.js	(revision 249)
+++ binary-improvements/webserver/js/stats.js	(revision 250)
@@ -25,2 +25,34 @@
 }
 
+function StartUIUpdatesModule () {
+	var updateGameTimeEvent = function() {
+		$.getJSON( "../api/getwebuiupdates?latestLine=" + lastLogLine)
+		.done(function(data) {
+			var time = "Day " + data.gametime.days + ", ";
+			if (data.gametime.hours < 10)
+				time += "0";
+			time += data.gametime.hours;
+			time += ":";
+			if (data.gametime.minutes < 10)
+				time += "0";
+			time += data.gametime.minutes;
+
+			$("#stats_time").html (time);
+			$("#stats_players").html (data.players);
+			$("#newlogcount").html (data.newlogs);
+			if (data.newlogs > 0) {
+				$("#newlogcount").addClass ("visible");
+			} else {
+				$("#newlogcount").removeClass ("visible");
+			}
+		})
+		.fail(function(jqxhr, textStatus, error) {
+			console.log("Error fetching ui updates");
+		})
+		.always(function() {
+		});
+		window.setTimeout(updateGameTimeEvent, 2000);
+	};
+	updateGameTimeEvent();
+}
+
Index: binary-improvements/webserver/js/tabs.js
===================================================================
--- binary-improvements/webserver/js/tabs.js	(revision 249)
+++ binary-improvements/webserver/js/tabs.js	(revision 250)
@@ -1,40 +1,94 @@
-var tabElements = {};
-var currentTabClass = "current_tab";
+$.widget( "7dtd.tabbedContent", {
+	options: {
+		contentdiv: null,
+		currentTabClass: "current_tab",
+		menuButtonClass: "menu_button",
+		allowedMenuButtonClass: "allowed",
+		contentDivClass: "contenttab",
+	},
+	
+	_create: function () {
+		var options = this.options;
+		var self = this;
+		
+		if (options.contentdiv == null) {
+			console.log ("contentdiv has to be set!");
+		}
+		
+		this.element.find ("ul > li").addClass (options.menuButtonClass);
 
-function OpenTab () {
-	var menuElement = $(this);
-	var linkElement = menuElement.children ("a");
-	var linkName = linkElement.attr ("href");
+		options.contentdiv.children ("div").addClass (options.contentDivClass);
+		this.element.on ('click.action', "ul > li", function (event) {
+			var menuElement = $(this);
+			var linkElement = menuElement.children ("a");
+			var linkName = linkElement.attr ("href");
+			self.openTab (linkName);
+		});
 
-	$("*").removeClass (currentTabClass);
-	menuElement.addClass (currentTabClass);
-	$(linkName).addClass (currentTabClass);
-}
+		self.tabs = {};
+		this.element.find (".menu_button").each (function () {
+			self.tabs [$(this).children ("a").attr ("href")] = $(this);
+		});
+	},
+	
+	applyPermissions: function () {
+		var self = this;
+		this.element.find (".menu_button").each (function () {
+			if ($(this).children ("a").data ("permission")) {
+				var perm = $(this).children ("a").data ("permission");
+				if (HasPermission (perm)) {
+					$(this).addClass (self.options.allowedMenuButtonClass);
+				}
+			} else {
+				$(this).addClass (self.options.allowedMenuButtonClass);
+			}
+		});
 
-function InitializeTabs () {
-	$("#adminmenu > ul > li").addClass ("menu_button");
-	$(".admincontent > div").addClass ("contenttab");
-	$(".menu_button").on ('click.action', null, function (event) {
-		var menuElement = $(this);
-		var linkElement = menuElement.children ("a");
-		var linkName = linkElement.attr ("href");
+		this.element.find ("." + self.options.allowedMenuButtonClass).first ().click ();
+	},
+	
+	openTab: function (name) {
+		if (name.indexOf ("#") != 0)
+			name = "#" + name;
 		
-		$("*").removeClass ("current_tab");
-		menuElement.addClass ("current_tab");
-		$(linkName).addClass ("current_tab");
-	});
-}
+		if (!this.tabs.hasOwnProperty(name)) {
+			console.log ("no tab named " + name + " in " + this);
+			return;
+		}
 
-function ApplyTabPermissions () {
-	$("#adminmenu .menu_button").each (function () {
-		if ($(this).children ("a").data ("permission")) {
-			var perm = $(this).children ("a").data ("permission");
-			if (HasPermission (perm)) {
-				$(this).addClass ("allowed");
-			}
+		var menuElement = $(".menu_button > a[href=" + name + "]").parent ();
+
+		$("*").removeClass (this.options.currentTabClass);
+		menuElement.addClass (this.options.currentTabClass);
+		$(name).addClass (this.options.currentTabClass);
+		var oldTab = this.currentTab;
+		this.currentTab = name;
+	
+		if (oldTab != name) {
+			this._trigger ("tabopened", null, { oldTab: oldTab, newTab: name } );
 		}
-	});
+	},
+	
+	currentOpenTab: function () {
+		return this.currentTab;
+	},
+	
+	isTabOpen: function (name) {
+		if (name.indexOf ("#") != 0)
+			name = "#" + name;
 
-	$("#adminmenu .allowed").first ().click ();
-}
+		return this.currentTab == name;
+	},
 
+/*
+	value: function (value) {
+		if ( value === undefined ) {
+			return this.options.value;
+		} else {
+			this.options.value = this._constrain( value );
+			var progress = this.options.value + "%";
+			this.element.text( progress );
+		}
+	},
+*/
+});
