Index: binary-improvements/webserver/css/style.css
===================================================================
--- binary-improvements/webserver/css/style.css	(revision 249)
+++ binary-improvements/webserver/css/style.css	(revision 250)
@@ -14,10 +14,10 @@
 
 a {
-	color: #ff6000;
+	color: orangered;
 	text-decoration: none;
 }
 
 a:visited {
-	color: #ff6000;
+	color: orangered;
 	text-decoration: none;
 }
@@ -30,5 +30,5 @@
 
 .adminnavbar,
-.admincontent {
+#admincontent {
 	position: absolute;
 	top: 0;
@@ -50,4 +50,5 @@
 	border-right: 1px solid rgba(0,0,0,0.3);
 	box-shadow: 3px 0px 14px rgba(0,0,0,0.9);
+	position: fixed;
 }
 
@@ -69,5 +70,5 @@
 
 .adminnavbar #adminmenu .menu_button.allowed {
-	display: inline;
+	display: list-item;
 }
 
@@ -80,4 +81,17 @@
 	font-weight: bold;
 	text-transform: uppercase;
+}
+
+#newlogcount {
+	font-size: 70%;
+	border-radius: 2px;
+	background-color: #f00;
+	color: #fff;
+	padding: 0px 2px 0px 2px;
+	display: none;
+}
+
+#newlogcount.visible {
+	display: inline;
 }
 
@@ -122,5 +136,5 @@
 */
 
-.admincontent {
+#admincontent {
 	position: absolute;
 	right: 0;
@@ -129,9 +143,9 @@
 }
 
-.admincontent #nopermissionwarning {
+#admincontent #nopermissionwarning {
 	margin: 20px 50px;
 }
 
-.admincontent .contenttab {
+#admincontent .contenttab {
 	position: absolute;
 	top: 0;
@@ -141,5 +155,5 @@
 }
 
-.admincontent .current_tab {
+#admincontent .current_tab {
 	display: block;
 }
@@ -199,7 +213,5 @@
 	border-collapse: collapse;
 }
-#equipmentTable .invFieldText {
-	display: none;
-}
+
 .playerInventoryDialog td.invField {
 	width: 58px;
@@ -222,4 +234,24 @@
 }
 
+#equipmentTable .invFieldText {
+	display: none;
+}
+.playerInventoryDialog .invFieldText { 
+	display: none;
+}
+.playerInventoryDialog .invFieldText.visible { 
+	display: inline;
+}
+.playerInventoryDialog .invFieldQuality {
+	bottom: 0px;
+	height: 5px;
+	left: 0px;
+	position: relative;
+	right: 0px;
+	display: none;
+}
+.playerInventoryDialog .invFieldQuality.visible {
+	display: block;
+}
 
 
@@ -246,5 +278,5 @@
 .adminmap .leaflet-container a:hover {
 	text-decoration: none;
-	color: #ff6000;
+	color: orangered;
 }
 
@@ -261,2 +293,95 @@
 }
 
+
+
+
+/*========================================
+-   Log
+*/
+
+.adminlog {
+	padding: 10px;
+}
+
+.adminlog table {
+	width: 100%;
+}
+
+.adminlog table td {
+	vertical-align: top;
+}
+
+.adminlog table tr.readmark td {
+	border-bottom-width: 2px;
+	border-bottom-color: red;
+	border-bottom-style: dotted;
+}
+
+.adminlog table tr.Log td {
+	color: limegreen;
+}
+.adminlog table tr.Warning td {
+	color: orange;
+}
+.adminlog table tr.Error td {
+	color: red;
+}
+.adminlog table tr.Exception td {
+	color: red;
+}
+
+
+.adminlog .logcol_datetime,
+.adminlog .logcol_uptime {
+	white-space: nowrap;
+	text-align: right;
+}
+.adminlog .logcol_type {
+	white-space: nowrap;
+}
+.adminlog .logcol_msg {
+	width: 100%;
+}
+
+.adminlog .logcol_missed {
+	text-align: center;
+	border-width: 1px 0px;
+	border-style: dashed;
+	border-color: orange;
+}
+
+.adminlog .tracebtn {
+	cursor: pointer;
+}
+.adminlog .tracebtn:after {
+	content: "Show trace...";
+}
+.adminlog .tracebtn.visible:after {
+	content: "Hide trace...";
+}
+
+.adminlog .trace {
+	display: none;
+}
+
+.adminlog .trace.visible {
+	display: block;
+}
+
+.adminlog .trace span {
+	display: block;
+	margin-left: 30px;
+	text-indent: -30px;
+}
+
+.adminlog #markasread {
+	cursor: pointer;
+	border-radius: 5px;
+	background-color: #444;
+	color: orangered;
+	display: inline-block;
+	margin-top: 10px;
+	padding: 3px 5px 3px 5px;
+}
+
+
Index: binary-improvements/webserver/index.html
===================================================================
--- binary-improvements/webserver/index.html	(revision 249)
+++ binary-improvements/webserver/index.html	(revision 250)
@@ -46,4 +46,5 @@
 	<script type="text/javascript" src="js/permissions.js"></script>
 	<script type="text/javascript" src="js/map.js"></script>
+	<script type="text/javascript" src="js/log.js"></script>
 
 	<!-- Own stylesheet -->
@@ -65,5 +66,5 @@
 				<ul>
 					<li><a href="#tab_map" data-permission="web.map">Map</a></li>
-					<li><a href="#tab_log" data-permission="web.log">Log</a></li>
+					<li><a href="#tab_log" data-permission="webapi.getlog">Log <span id="newlogcount"></span></a></li>
 				</ul>
 			</div>
@@ -85,8 +86,18 @@
 			</div>
 		</div>
-		<div class="admincontent">
+		<div id="admincontent">
 			<h1 id="nopermissionwarning" style="display:none">An error occured or you do not have any permissions on this WebPanel. Log in with the link on the lower left!</h1>
 			<div id="tab_map" class="adminmap"></div>
-			<div id="tab_log" class="adminlog"></div>
+			<div id="tab_log" class="adminlog">
+				<table>
+					<tr>
+						<th>Date/Time</th>
+						<th>Uptime</th>
+						<th>Severity</th>
+						<th>Message</th>
+					</tr>
+				</table>
+				<a id="markasread">Mark as read</a>
+			</div>
 		</div>
 	</div>
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 );
+		}
+	},
+*/
+});
