Index: /branches/HiveStatistics/sources/HeuristicLab 3.3 Services.sln
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab 3.3 Services.sln	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab 3.3 Services.sln	(revision 12516)
@@ -49,6 +49,4 @@
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.Services.Hive.JanitorService-3.3", "HeuristicLab.Services.Hive.JanitorService\3.3\HeuristicLab.Services.Hive.JanitorService-3.3.csproj", "{F685556B-F6EB-4143-8E7E-662D00FDD299}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.GeoIP", "HeuristicLab.GeoIP\1.12\HeuristicLab.GeoIP.csproj", "{BE9B0229-9150-49A4-AEA6-BB58E055992F}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.Services.Access.DataAccess-3.3", "HeuristicLab.Services.Access.DataAccess\3.3\HeuristicLab.Services.Access.DataAccess-3.3.csproj", "{0F652437-998A-4EAB-8BF1-444B5FE8CE97}"
 EndProject
@@ -69,4 +67,6 @@
 		.nuget\NuGet.targets = .nuget\NuGet.targets
 	EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.GeoIP", "HeuristicLab.ExtLibs\HeuristicLab.GeoIP\1.12\HeuristicLab.GeoIP.csproj", "{BE9B0229-9150-49A4-AEA6-BB58E055992F}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.Services.WebApp.Statistics-3.3", "HeuristicLab.Services.WebApp.Statistics\3.3\HeuristicLab.Services.WebApp.Statistics-3.3.csproj", "{1BC54353-7D4C-4734-BAA9-3D11B3242B59}"
@@ -161,16 +161,4 @@
 		{F685556B-F6EB-4143-8E7E-662D00FDD299}.Release|x86.ActiveCfg = Release|x86
 		{F685556B-F6EB-4143-8E7E-662D00FDD299}.Release|x86.Build.0 = Release|x86
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x64.ActiveCfg = Release|x64
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x64.Build.0 = Release|x64
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x86.ActiveCfg = Release|x86
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x86.Build.0 = Release|x86
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|Any CPU.Build.0 = Release|Any CPU
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x64.ActiveCfg = Release|x64
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x64.Build.0 = Release|x64
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x86.ActiveCfg = Release|x86
-		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x86.Build.0 = Release|x86
 		{0F652437-998A-4EAB-8BF1-444B5FE8CE97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{0F652437-998A-4EAB-8BF1-444B5FE8CE97}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -245,12 +233,28 @@
 		{2351DBE7-D0E1-44F9-9CE9-2F0F5E580572}.Release|x86.ActiveCfg = Release|x86
 		{2351DBE7-D0E1-44F9-9CE9-2F0F5E580572}.Release|x86.Build.0 = Release|x86
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x64.ActiveCfg = Debug|x64
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x64.Build.0 = Debug|x64
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x86.ActiveCfg = Debug|x86
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Debug|x86.Build.0 = Debug|x86
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x64.ActiveCfg = Release|x64
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x64.Build.0 = Release|x64
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x86.ActiveCfg = Release|x86
+		{BE9B0229-9150-49A4-AEA6-BB58E055992F}.Release|x86.Build.0 = Release|x86
 		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x64.ActiveCfg = Debug|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x64.Build.0 = Debug|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x86.ActiveCfg = Debug|x86
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Debug|x86.Build.0 = Debug|x86
 		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|Any CPU.Build.0 = Release|Any CPU
-		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x64.ActiveCfg = Release|Any CPU
-		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x86.ActiveCfg = Release|Any CPU
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x64.ActiveCfg = Release|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x64.Build.0 = Release|x64
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x86.ActiveCfg = Release|x86
+		{1BC54353-7D4C-4734-BAA9-3D11B3242B59}.Release|x86.Build.0 = Release|x86
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimClientDao.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimClientDao.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimClientDao.cs	(revision 12516)
@@ -37,6 +37,10 @@
       return Table.SingleOrDefault(x => x.Id == id);
     }
-    public IQueryable<DimClient> GetRecentlyAddedClients() {
+    public IQueryable<DimClient> GetActiveClients() {
       return Table.Where(x => x.ExpirationTime == null);
+    }
+
+    public IQueryable<DimClient> GetExpiredClients() {
+      return Table.Where(x => x.ExpirationTime != null);
     }
 
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/DimJobDao.cs	(revision 12516)
@@ -35,4 +35,12 @@
       return Table.Where(x => x.UserId == id);
     }
+
+    public IQueryable<DimJob> GetNotCompletedJobs() {
+      return Table.Where(x => x.DateCompleted == null);
+    }
+
+    public IQueryable<DimJob> GetCompletedJobs() {
+      return Table.Where(x => x.DateCompleted != null);
+    }
   }
 }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactClientInfoDao.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactClientInfoDao.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactClientInfoDao.cs	(revision 12516)
@@ -59,4 +59,8 @@
              };
     }
+
+    public IQueryable<FactClientInfo> GetByClientId(Guid id) {
+      return Table.Where(x => x.ClientId == id);
+    }
   }
 }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactTaskDao.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactTaskDao.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/HiveStatistics/FactTaskDao.cs	(revision 12516)
@@ -44,4 +44,12 @@
     }
 
+    public DateTime? GetLastCompletedTaskFromJob(Guid id) {
+      return Table.Where(x => x.JobId == id && x.EndTime != null).Max(x => x.EndTime);
+    }
+
+    public IQueryable<FactTask> GetByClientId(Guid id) {
+      return Table.Where(x => x.LastClientId == id);
+    }
+
     public override void Delete(IEnumerable<Guid> ids) {
       string paramIds = string.Join(",", ids.Select(x => string.Format("'{0}'", x)));
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/Daos/TaskDao.cs	(revision 12516)
@@ -35,4 +35,8 @@
       return Table.Where(x => !x.IsParentTask);
     }
+
+    public IQueryable<Task> GetAllByJobId(Guid id) {
+      return Table.Where(x => x.JobId == id);
+    }
   }
 }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml	(revision 12516)
@@ -218,4 +218,6 @@
       <Column Name="ResourceGroupId" Type="System.Guid" DbType="UniqueIdentifier" CanBeNull="true" />
       <Column Name="ResourceGroup2Id" Type="System.Guid" DbType="UniqueIdentifier" CanBeNull="true" />
+      <Column Name="GroupName" Type="System.String" CanBeNull="true" />
+      <Column Name="GroupName2" Type="System.String" CanBeNull="true" />
       <Association Name="DimClient_FactTask" Member="FactTasks" ThisKey="Id" OtherKey="LastClientId" Type="FactTask" />
       <Association Name="DimClient_FactClientInfo" Member="FactClientInfos" ThisKey="Id" OtherKey="ClientId" Type="FactClientInfo" />
@@ -250,5 +252,8 @@
       <Column Name="JobName" Type="System.String" DbType="VarChar(MAX) NOT NULL" CanBeNull="false" />
       <Column Name="UserName" Type="System.String" DbType="VarChar(MAX) NOT NULL" CanBeNull="false" />
-      <Column Name="DateCreated" Type="System.DateTime" CanBeNull="false" />
+      <Column Name="DateCreated" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
+      <Column Name="TotalTasks" Type="System.Int32" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="CompletedTasks" Type="System.Int32" DbType="INT NOT NULL" CanBeNull="false" />
+      <Column Name="DateCompleted" Type="System.DateTime" DbType="DateTime NULL" CanBeNull="true" />
       <Association Name="DimJob_FactTask" Member="FactTasks" ThisKey="JobId" OtherKey="JobId" Type="FactTask" />
     </Type>
@@ -283,6 +288,4 @@
       <Column Name="CpuUtilization" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
       <Column Name="TotalTimeIdle" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
-      <Column Name="TotalTimeCalculating" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
-      <Column Name="TotalTimeTransferring" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
       <Column Name="TotalTimeOffline" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
       <Column Name="TotalTimeUnavailable" Type="System.Double" DbType="Float NOT NULL" CanBeNull="false" />
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml.layout
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml.layout	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.dbml.layout	(revision 12516)
@@ -1,4 +1,4 @@
 ﻿<?xml version="1.0" encoding="utf-8"?>
-<ordesignerObjectsDiagram dslVersion="1.0.0.0" absoluteBounds="0, 0, 24.25, 17.5" name="HiveDataContext">
+<ordesignerObjectsDiagram dslVersion="1.0.0.0" absoluteBounds="0, 0, 24.25, 17.875" name="HiveDataContext">
   <DataContextMoniker Name="/HiveDataContext" />
   <nestedChildShapes>
@@ -247,8 +247,8 @@
       </nestedChildShapes>
     </classShape>
-    <classShape Id="b5b919c2-4efc-4f09-8f52-9d541a11e961" absoluteBounds="4.625, 15.25, 2, 1.9631982421875023">
+    <classShape Id="b5b919c2-4efc-4f09-8f52-9d541a11e961" absoluteBounds="4.625, 15.25, 2, 2.3478011067708344">
       <DataClassMoniker Name="/HiveDataContext/DimClient" />
       <nestedChildShapes>
-        <elementListCompartment Id="30f62a7b-0b16-404e-b972-fb12bfe978dd" absoluteBounds="4.6400000000000006, 15.71, 1.9700000000000002, 1.4031982421875" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+        <elementListCompartment Id="30f62a7b-0b16-404e-b972-fb12bfe978dd" absoluteBounds="4.6400000000000006, 15.71, 1.9700000000000002, 1.7878011067708333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
       </nestedChildShapes>
     </classShape>
@@ -259,8 +259,8 @@
       </nestedChildShapes>
     </classShape>
-    <classShape Id="e0cb8641-a75e-4b9f-beda-3218c56938b1" absoluteBounds="8.125, 9.125, 2, 1.7708968098958344">
+    <classShape Id="e0cb8641-a75e-4b9f-beda-3218c56938b1" absoluteBounds="8.125, 8.25, 2, 2.3478011067708344">
       <DataClassMoniker Name="/HiveDataContext/DimJob" />
       <nestedChildShapes>
-        <elementListCompartment Id="6b9e8260-7e4b-4357-9c26-eebebfd69504" absoluteBounds="8.14, 9.585, 1.9700000000000002, 1.2108968098958333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+        <elementListCompartment Id="6b9e8260-7e4b-4357-9c26-eebebfd69504" absoluteBounds="8.14, 8.71, 1.9700000000000002, 1.7878011067708333" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
       </nestedChildShapes>
     </classShape>
@@ -277,8 +277,8 @@
       </nestedChildShapes>
     </classShape>
-    <classShape Id="053a5cff-b18a-4ee5-8d43-48ed5c5dcfad" absoluteBounds="2.625, 11, 2, 3.5016097005208326">
+    <classShape Id="053a5cff-b18a-4ee5-8d43-48ed5c5dcfad" absoluteBounds="2.625, 11, 2, 3.1170068359375005">
       <DataClassMoniker Name="/HiveDataContext/FactClientInfo" />
       <nestedChildShapes>
-        <elementListCompartment Id="0a1cc913-6636-455c-95b3-302f533527db" absoluteBounds="2.6399999999999997, 11.46, 1.9700000000000002, 2.941609700520833" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
+        <elementListCompartment Id="0a1cc913-6636-455c-95b3-302f533527db" absoluteBounds="2.6399999999999997, 11.46, 1.9700000000000002, 2.5570068359375" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" />
       </nestedChildShapes>
     </classShape>
@@ -290,5 +290,5 @@
       </nodes>
     </associationConnector>
-    <associationConnector edgePoints="[(8.5 : 10.8958968098958); (8.5 : 11.25)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+    <associationConnector edgePoints="[(8.5 : 10.5978011067708); (8.5 : 11.25)]" fixedFrom="NotFixed" fixedTo="NotFixed">
       <AssociationMoniker Name="/HiveDataContext/DimJob/DimJob_FactTask" />
       <nodes>
@@ -297,5 +297,5 @@
       </nodes>
     </associationConnector>
-    <associationConnector edgePoints="[(5.99218625 : 15.25); (5.99218625 : 12.1788329785157); (4.625 : 12.1788329785157)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+    <associationConnector edgePoints="[(5.6249975 : 15.25); (5.6249975 : 12.5585034179688); (4.625 : 12.5585034179688)]" fixedFrom="Algorithm" fixedTo="Algorithm">
       <AssociationMoniker Name="/HiveDataContext/DimClient/DimClient_FactClientInfo" />
       <nodes>
@@ -311,5 +311,5 @@
       </nodes>
     </associationConnector>
-    <associationConnector edgePoints="[(1.59375 : 10.0689925130208); (1.59375 : 12.7508048502604); (2.625 : 12.7508048502604)]" fixedFrom="NotFixed" fixedTo="NotFixed">
+    <associationConnector edgePoints="[(1.59375 : 10.0689925130208); (1.59375 : 12.5585034179688); (2.625 : 12.5585034179688)]" fixedFrom="Algorithm" fixedTo="Algorithm">
       <AssociationMoniker Name="/HiveDataContext/DimUser/DimUser_FactClientInfo" />
       <nodes>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.designer.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.designer.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/HiveDataContext.designer.cs	(revision 12516)
@@ -4619,4 +4619,8 @@
 		private System.Nullable<System.Guid> _ResourceGroup2Id;
 		
+		private string _GroupName;
+		
+		private string _GroupName2;
+		
 		private EntitySet<FactTask> _FactTasks;
 		
@@ -4639,4 +4643,8 @@
     partial void OnResourceGroup2IdChanging(System.Nullable<System.Guid> value);
     partial void OnResourceGroup2IdChanged();
+    partial void OnGroupNameChanging(string value);
+    partial void OnGroupNameChanged();
+    partial void OnGroupName2Changing(string value);
+    partial void OnGroupName2Changed();
     #endregion
 		
@@ -4764,4 +4772,44 @@
 					this.SendPropertyChanged("ResourceGroup2Id");
 					this.OnResourceGroup2IdChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_GroupName")]
+		public string GroupName
+		{
+			get
+			{
+				return this._GroupName;
+			}
+			set
+			{
+				if ((this._GroupName != value))
+				{
+					this.OnGroupNameChanging(value);
+					this.SendPropertyChanging();
+					this._GroupName = value;
+					this.SendPropertyChanged("GroupName");
+					this.OnGroupNameChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_GroupName2")]
+		public string GroupName2
+		{
+			get
+			{
+				return this._GroupName2;
+			}
+			set
+			{
+				if ((this._GroupName2 != value))
+				{
+					this.OnGroupName2Changing(value);
+					this.SendPropertyChanging();
+					this._GroupName2 = value;
+					this.SendPropertyChanged("GroupName2");
+					this.OnGroupName2Changed();
 				}
 			}
@@ -5359,4 +5407,10 @@
 		private System.DateTime _DateCreated;
 		
+		private int _TotalTasks;
+		
+		private int _CompletedTasks;
+		
+		private System.Nullable<System.DateTime> _DateCompleted;
+		
 		private EntitySet<FactTask> _FactTasks;
 		
@@ -5375,4 +5429,10 @@
     partial void OnDateCreatedChanging(System.DateTime value);
     partial void OnDateCreatedChanged();
+    partial void OnTotalTasksChanging(int value);
+    partial void OnTotalTasksChanged();
+    partial void OnCompletedTasksChanging(int value);
+    partial void OnCompletedTasksChanged();
+    partial void OnDateCompletedChanging(System.Nullable<System.DateTime> value);
+    partial void OnDateCompletedChanged();
     #endregion
 		
@@ -5463,5 +5523,5 @@
 		}
 		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DateCreated")]
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DateCreated", DbType="DateTime NOT NULL")]
 		public System.DateTime DateCreated
 		{
@@ -5479,4 +5539,64 @@
 					this.SendPropertyChanged("DateCreated");
 					this.OnDateCreatedChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTasks", DbType="INT NOT NULL")]
+		public int TotalTasks
+		{
+			get
+			{
+				return this._TotalTasks;
+			}
+			set
+			{
+				if ((this._TotalTasks != value))
+				{
+					this.OnTotalTasksChanging(value);
+					this.SendPropertyChanging();
+					this._TotalTasks = value;
+					this.SendPropertyChanged("TotalTasks");
+					this.OnTotalTasksChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_CompletedTasks", DbType="INT NOT NULL")]
+		public int CompletedTasks
+		{
+			get
+			{
+				return this._CompletedTasks;
+			}
+			set
+			{
+				if ((this._CompletedTasks != value))
+				{
+					this.OnCompletedTasksChanging(value);
+					this.SendPropertyChanging();
+					this._CompletedTasks = value;
+					this.SendPropertyChanged("CompletedTasks");
+					this.OnCompletedTasksChanged();
+				}
+			}
+		}
+		
+		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_DateCompleted", DbType="DateTime NULL")]
+		public System.Nullable<System.DateTime> DateCompleted
+		{
+			get
+			{
+				return this._DateCompleted;
+			}
+			set
+			{
+				if ((this._DateCompleted != value))
+				{
+					this.OnDateCompletedChanging(value);
+					this.SendPropertyChanging();
+					this._DateCompleted = value;
+					this.SendPropertyChanged("DateCompleted");
+					this.OnDateCompletedChanged();
 				}
 			}
@@ -5876,8 +5996,4 @@
 		
 		private double _TotalTimeIdle;
-		
-		private double _TotalTimeCalculating;
-		
-		private double _TotalTimeTransferring;
 		
 		private double _TotalTimeOffline;
@@ -5915,8 +6031,4 @@
     partial void OnTotalTimeIdleChanging(double value);
     partial void OnTotalTimeIdleChanged();
-    partial void OnTotalTimeCalculatingChanging(double value);
-    partial void OnTotalTimeCalculatingChanged();
-    partial void OnTotalTimeTransferringChanging(double value);
-    partial void OnTotalTimeTransferringChanged();
     partial void OnTotalTimeOfflineChanging(double value);
     partial void OnTotalTimeOfflineChanged();
@@ -6127,44 +6239,4 @@
 		}
 		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTimeCalculating", DbType="Float NOT NULL")]
-		public double TotalTimeCalculating
-		{
-			get
-			{
-				return this._TotalTimeCalculating;
-			}
-			set
-			{
-				if ((this._TotalTimeCalculating != value))
-				{
-					this.OnTotalTimeCalculatingChanging(value);
-					this.SendPropertyChanging();
-					this._TotalTimeCalculating = value;
-					this.SendPropertyChanged("TotalTimeCalculating");
-					this.OnTotalTimeCalculatingChanged();
-				}
-			}
-		}
-		
-		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTimeTransferring", DbType="Float NOT NULL")]
-		public double TotalTimeTransferring
-		{
-			get
-			{
-				return this._TotalTimeTransferring;
-			}
-			set
-			{
-				if ((this._TotalTimeTransferring != value))
-				{
-					this.OnTotalTimeTransferringChanging(value);
-					this.SendPropertyChanging();
-					this._TotalTimeTransferring = value;
-					this.SendPropertyChanged("TotalTimeTransferring");
-					this.OnTotalTimeTransferringChanged();
-				}
-			}
-		}
-		
 		[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_TotalTimeOffline", DbType="Float NOT NULL")]
 		public double TotalTimeOffline
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Initialize Hive Database.sql
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Initialize Hive Database.sql	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive.DataAccess/3.3/SQL Scripts/Initialize Hive Database.sql	(revision 12516)
@@ -217,12 +217,17 @@
     [ResourceGroupId]  UNIQUEIDENTIFIER NULL,
     [ResourceGroup2Id] UNIQUEIDENTIFIER NULL,
+	[GroupName]        VARCHAR (MAX)    NULL,
+	[GroupName2]       VARCHAR (MAX)    NULL,
     CONSTRAINT [PK_DimClient] PRIMARY KEY CLUSTERED ([Id] ASC)
 );
 CREATE TABLE [statistics].[DimJob] (
-    [JobId]       UNIQUEIDENTIFIER NOT NULL,
-    [UserId]      UNIQUEIDENTIFIER NOT NULL,
-    [JobName]     VARCHAR (MAX)    NOT NULL,
-    [UserName]    VARCHAR (MAX)    NOT NULL,
-    [DateCreated] DATETIME		   NOT NULL,
+    [JobId]          UNIQUEIDENTIFIER NOT NULL,
+    [UserId]         UNIQUEIDENTIFIER NOT NULL,
+    [JobName]        VARCHAR (MAX)    NOT NULL,
+    [UserName]       VARCHAR (MAX)    NOT NULL,
+    [DateCreated]    DATETIME		  NOT NULL,
+    [TotalTasks]     INT              NOT NULL,
+    [CompletedTasks] INT              NOT NULL,
+    [DateCompleted]  DATETIME		  NULL,
     CONSTRAINT [PK_DimJob] PRIMARY KEY CLUSTERED ([JobId] ASC)
 );
@@ -243,6 +248,4 @@
     [SlaveState]			VarChar(15) 	 NOT NULL,
     [TotalTimeIdle]         FLOAT (53)       NOT NULL,
-    [TotalTimeCalculating]  FLOAT (53)       NOT NULL,
-    [TotalTimeTransferring] FLOAT (53)       NOT NULL,
     [TotalTimeOffline]      FLOAT (53)       NOT NULL,
     [TotalTimeUnavailable]  FLOAT (53)       NOT NULL,
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive/3.3/HiveDao.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive/3.3/HiveDao.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive/3.3/HiveDao.cs	(revision 12516)
@@ -317,5 +317,5 @@
       exp.JobCount = jobs.Count();
       exp.CalculatingCount = jobs.Count(j => j.State == TaskState.Calculating);
-      exp.FinishedCount = jobs.Count(j => j.State == TaskState.Finished);
+      exp.FinishedCount = jobs.Count(j => j.State == TaskState.Finished || j.State == TaskState.Aborted || j.State == TaskState.Failed);
       return exp;
     }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.Hive/3.3/HiveStatisticsGenerator.cs	(revision 12516)
@@ -32,4 +32,5 @@
     private const string UnknownUserName = "Unknown";
     private static readonly TimeSpan SmallestTimeSpan = new TimeSpan(0, 5, 0);
+    private static readonly TaskState[] CompletedStates = { TaskState.Finished, TaskState.Aborted, TaskState.Failed };
 
     public void GenerateStatistics() {
@@ -42,11 +43,21 @@
         });
 
+        DimTime time = null;
         pm.UseTransaction(() => {
-          var newTime = UpdateDimTimeTable(pm);
-          if (newTime != null) {
+          time = UpdateDimTimeTable(pm);
+          pm.SubmitChanges();
+        });
+
+        if (time != null) {
+          pm.UseTransaction(() => {
+            UpdateFactClientInfoTable(time, pm);
             pm.SubmitChanges();
-            UpdateFactClientInfoTable(newTime, pm);
-            UpdateTaskFactsTable(newTime, pm);
+          });
+
+          pm.UseTransaction(() => {
+            UpdateTaskFactsTable(time, pm);
             try {
+              pm.SubmitChanges();
+              UpdateExistingDimJobs(pm);
               pm.SubmitChanges();
             }
@@ -58,6 +69,6 @@
                           Exception Details: {0}", e));
             }
-          }
-        });
+          });
+        }
       }
     }
@@ -97,4 +108,5 @@
       var dimJobDao = pm.DimJobDao;
       var jobDao = pm.JobDao;
+      var taskDao = pm.TaskDao;
       var dimJobIds = dimJobDao.GetAll().Select(x => x.JobId);
       var newJobs = jobDao.GetAll()
@@ -104,5 +116,6 @@
           UserId = x.OwnerUserId,
           JobName = x.Name ?? string.Empty,
-          DateCreated = x.DateCreated
+          DateCreated = x.DateCreated,
+          TotalTasks = taskDao.GetAll().Count(y => y.JobId == x.JobId)
         })
         .ToList();
@@ -112,6 +125,34 @@
         UserId = x.UserId,
         UserName = GetUserName(x.UserId),
-        DateCreated = x.DateCreated
+        DateCreated = x.DateCreated,
+        TotalTasks = x.TotalTasks,
+        CompletedTasks = 0,
+        DateCompleted = null
       }));
+    }
+
+    private void UpdateExistingDimJobs(PersistenceManager pm) {
+      var dimJobDao = pm.DimJobDao;
+      var factTaskDao = pm.FactTaskDao;
+      foreach (var dimJob in dimJobDao.GetNotCompletedJobs()) {
+        var taskStates = factTaskDao.GetByJobId(dimJob.JobId)
+            .GroupBy(x => x.TaskState)
+            .Select(x => new {
+              State = x.Key,
+              Count = x.Count()
+            }).ToList();
+        int totalTasks = 0, completedTasks = 0;
+        foreach (var state in taskStates) {
+          totalTasks += state.Count;
+          if (CompletedStates.Contains(state.State)) {
+            completedTasks += state.Count;
+          }
+        }
+        if (totalTasks == completedTasks) {
+          dimJob.DateCompleted = factTaskDao.GetLastCompletedTaskFromJob(dimJob.JobId);
+        }
+        dimJob.TotalTasks = totalTasks;
+        dimJob.CompletedTasks = completedTasks;
+      }
     }
 
@@ -120,5 +161,5 @@
       var slaveDao = pm.SlaveDao;
       var slaves = slaveDao.GetAll();
-      var recentlyAddedClients = dimClientDao.GetRecentlyAddedClients();
+      var recentlyAddedClients = dimClientDao.GetActiveClients();
       var slaveIds = slaves.Select(x => x.ResourceId);
 
@@ -136,5 +177,5 @@
           ClientId = client.Id
         };
-      var clientIds = dimClientDao.GetAll().Select(x => x.ResourceId);
+      var clientIds = dimClientDao.GetActiveClients().Select(x => x.ResourceId);
       var modifiedClientIds = modifiedClients.Select(x => x.SlaveId);
       var newClients = slaves
@@ -145,5 +186,7 @@
           x.Name,
           ResourceGroupId = x.ParentResourceId,
-          ResourceGroup2Id = x.ParentResource.ParentResourceId
+          GroupName = x.ParentResource.Name,
+          ResourceGroup2Id = x.ParentResource.ParentResourceId,
+          GroupName2 = x.ParentResource.ParentResource.Name
         })
         .ToList();
@@ -156,5 +199,7 @@
         ExpirationTime = null,
         ResourceGroupId = x.ResourceGroupId,
-        ResourceGroup2Id = x.ResourceGroup2Id
+        GroupName = x.GroupName,
+        ResourceGroup2Id = x.ResourceGroup2Id,
+        GroupName2 = x.GroupName2
       }));
     }
@@ -167,5 +212,5 @@
       var newRawFactInfos =
         from s in slaveDao.GetAll()
-        join c in dimClientDao.GetRecentlyAddedClients() on s.ResourceId equals c.ResourceId
+        join c in dimClientDao.GetActiveClients() on s.ResourceId equals c.ResourceId
         join lcf in factClientInfoDao.GetLastUpdateTimestamps() on c.ResourceId equals lcf.ResourceId into joinCf
         from cf in joinCf.DefaultIfEmpty()
@@ -186,6 +231,6 @@
         from x in newRawFactInfos.ToList()
         let duration = x.LastFactTimestamp != null
-                       ? (newTime.Time - (DateTime)x.LastFactTimestamp).TotalMinutes
-                       : SmallestTimeSpan.TotalMinutes
+                       ? (int)(newTime.Time - (DateTime)x.LastFactTimestamp).TotalSeconds
+                       : (int)SmallestTimeSpan.TotalSeconds
         select new FactClientInfo {
           ClientId = x.ClientId,
@@ -193,5 +238,5 @@
           UserId = x.UserId,
           NumUsedCores = x.TotalCores - x.FreeCores,
-          NumTotalCores = x.TotalMemory,
+          NumTotalCores = x.TotalCores,
           UsedMemory = x.TotalMemory - x.FreeMemory,
           TotalMemory = x.TotalMemory,
@@ -199,5 +244,4 @@
           SlaveState = x.SlaveState,
           TotalTimeIdle = x.SlaveState == SlaveState.Idle && x.IsAllowedToCalculate ? duration : 0.0,
-          TotalTimeCalculating = x.SlaveState == SlaveState.Calculating ? duration : 0.0,
           TotalTimeUnavailable = x.SlaveState == SlaveState.Idle && !x.IsAllowedToCalculate ? duration : 0.0,
           TotalTimeOffline = x.SlaveState == SlaveState.Offline ? duration : 0.0
@@ -226,5 +270,5 @@
         join lastFactTask in notFinishedFactTasks on task.TaskId equals lastFactTask.TaskId into lastFactPerTask
         from lastFact in lastFactPerTask.DefaultIfEmpty()
-        join client in dimClientDao.GetRecentlyAddedClients() on lastSlaveId equals client.ResourceId into clientsPerSlaveId
+        join client in dimClientDao.GetActiveClients() on lastSlaveId equals client.ResourceId into clientsPerSlaveId
         from client in clientsPerSlaveId.DefaultIfEmpty()
         select new {
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.OKB/3.3/HeuristicLab.Services.OKB-3.3.csproj
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.OKB/3.3/HeuristicLab.Services.OKB-3.3.csproj	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.OKB/3.3/HeuristicLab.Services.OKB-3.3.csproj	(revision 12516)
@@ -315,8 +315,4 @@
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\..\HeuristicLab.GeoIP\1.12\HeuristicLab.GeoIP.csproj">
-      <Project>{BE9B0229-9150-49A4-AEA6-BB58E055992F}</Project>
-      <Name>HeuristicLab.GeoIP</Name>
-    </ProjectReference>
     <ProjectReference Include="..\..\HeuristicLab.Services.Access.DataAccess\3.3\HeuristicLab.Services.Access.DataAccess-3.3.csproj">
       <Project>{0F652437-998A-4EAB-8BF1-444B5FE8CE97}</Project>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/HeuristicLab.Services.WebApp.Statistics-3.3.csproj
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/HeuristicLab.Services.WebApp.Statistics-3.3.csproj	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/HeuristicLab.Services.WebApp.Statistics-3.3.csproj	(revision 12516)
@@ -140,10 +140,22 @@
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="WebApi\DataController.cs" />
+    <Compile Include="WebApi\ClientController.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="WebApi\DataTransfer\Client.cs" />
+    <Compile Include="WebApi\DataTransfer\ClientDetails.cs" />
+    <Compile Include="WebApi\DataTransfer\ClientPage.cs" />
+    <Compile Include="WebApi\DataTransfer\ClientStatus.cs" />
+    <Compile Include="WebApi\DataTransfer\Integer.cs" />
     <Compile Include="WebApi\DataTransfer\Job.cs" />
+    <Compile Include="WebApi\DataTransfer\JobDetails.cs" />
+    <Compile Include="WebApi\DataTransfer\JobPage.cs" />
     <Compile Include="WebApi\DataTransfer\Task.cs" />
+    <Compile Include="WebApi\DataTransfer\TaskPage.cs" />
+    <Compile Include="WebApi\DataTransfer\TaskStateCount.cs" />
     <Compile Include="WebApi\DataTransfer\User.cs" />
+    <Compile Include="WebApi\JavascriptUtils.cs" />
+    <Compile Include="WebApi\JobController.cs" />
+    <Compile Include="WebApi\TaskController.cs" />
+    <Compile Include="WebApi\UserController.cs" />
   </ItemGroup>
   <ItemGroup>
@@ -151,14 +163,41 @@
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="WebApp\clients\details\clientDetailsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="WebApp\clients\clientsCtrl.js">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="WebApp\clients\details\clientTaskDetailsDialogCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\jobs\details\jobDetailsCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\jobs\details\jobTaskDetailsDialogCtrl.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="WebApp\jobs\jobsCtrl.js">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="WebApp\services\clientService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\jobService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="WebApp\services\statisticsService.js">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="WebApp\services\taskService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\services\userService.js">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="WebApp\statistics.css">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="WebApp\users\details\userDetailsCtrl.js">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
@@ -179,4 +218,19 @@
     </Content>
     <Content Include="WebApp\users\users.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <None Include="WebApp\clients\details\clientDetails.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\clients\details\clientTaskDetailsDialog.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\jobs\details\jobDetails.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Include="WebApp\jobs\details\jobTaskDetailsDialog.cshtml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <Content Include="WebApp\users\details\userDetails.cshtml">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ClientController.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ClientController.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/ClientController.cs	(revision 12516)
@@ -0,0 +1,189 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using HeuristicLab.Services.WebApp.Status.WebApi;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+
+  [Authorize]
+  public class ClientController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public DT.ClientDetails GetClientDetails(Guid id) {
+      using (var pm = PersistenceManager) {
+        var dimClientDao = pm.DimClientDao;
+        var factClientInfoDao = pm.FactClientInfoDao;
+        var factTaskDao = pm.FactTaskDao;
+        return pm.UseTransaction(() => {
+          var timeData = factClientInfoDao.GetByClientId(id)
+            .GroupBy(x => x.ClientId)
+            .Select(x => new {
+              TotalIdleTime = (long)x.Sum(y => y.TotalTimeIdle),
+              TotalOfflineTime = (long)x.Sum(y => y.TotalTimeOffline),
+              TotalUnavailableTime = (long)x.Sum(y => y.TotalTimeUnavailable)
+            }).FirstOrDefault();
+          var taskTimeData = factTaskDao.GetByClientId(id)
+            .GroupBy(x => x.LastClientId)
+            .Select(x => new {
+              TotalCalculatingTime = (long)x.Sum(y => y.CalculatingTime),
+              TotalTransferringTime = (long)x.Sum(y => y.TransferTime)
+            }).FirstOrDefault();
+          var startDate = factClientInfoDao.GetByClientId(id)
+            .Where(x => x.SlaveState == SlaveState.Offline)
+            .OrderByDescending(x => x.Time)
+            .Select(x => x.Time).FirstOrDefault();
+          long upTime = 0;
+          if (startDate == default(DateTime)) {
+            startDate = factClientInfoDao.GetByClientId(id)
+              .OrderByDescending(x => x.Time)
+              .Select(x => x.Time)
+              .FirstOrDefault();
+          }
+          if (startDate != default(DateTime)) {
+            upTime = (long)(DateTime.Now - startDate).TotalSeconds;
+          }
+          return (from client in dimClientDao.GetAll().Where(x => x.Id == id)
+                  join info in factClientInfoDao.GetAll()
+                    on client.Id equals info.ClientId into clientInfoJoin
+                  from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                  let offline = (client.ExpirationTime != null || clientInfo.SlaveState == SlaveState.Offline)
+                  select new DT.ClientDetails {
+                    Id = client.Id,
+                    Name = client.Name,
+                    TotalCores = clientInfo.NumTotalCores,
+                    UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                    TotalMemory = clientInfo.TotalMemory,
+                    UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                    CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                    State = clientInfo.SlaveState.ToString(),
+                    LastUpdate = clientInfo.Time,
+                    UpTime = offline ? 0 : upTime,
+                    TotalUnavailableTime = timeData != null ? timeData.TotalUnavailableTime : 0,
+                    TotalCalculatingTime = taskTimeData != null ? taskTimeData.TotalCalculatingTime : 0,
+                    TotalIdleTime = timeData != null ? timeData.TotalIdleTime : 0,
+                    TotalOfflineTime = timeData != null ? timeData.TotalOfflineTime : 0,
+                    TotalTransferringTime = taskTimeData != null ? taskTimeData.TotalTransferringTime : 0,
+                    TasksStates = factTaskDao.GetByClientId(id)
+                                    .GroupBy(x => x.TaskState)
+                                    .Select(x => new DT.TaskStateCount {
+                                      State = x.Key.ToString(),
+                                      Count = x.Count()
+                                    }).ToList()
+                  })
+                  .FirstOrDefault();
+        });
+      }
+    }
+
+    public DT.ClientPage GetClients(int page, int size, bool expired = false) {
+      using (var pm = PersistenceManager) {
+        var dimClientDao = pm.DimClientDao;
+        var factClientInfoDao = pm.FactClientInfoDao;
+        return pm.UseTransaction(() => {
+          var clients = expired ? dimClientDao.GetExpiredClients() : dimClientDao.GetActiveClients();
+          var query = (from client in clients
+                       join info in factClientInfoDao.GetAll()
+                         on client.Id equals info.ClientId into clientInfoJoin
+                       from clientInfo in clientInfoJoin.OrderByDescending(x => x.Time).Take(1)
+                       let offline = (expired || clientInfo.SlaveState == SlaveState.Offline)
+                       select new DT.Client {
+                         Id = client.Id,
+                         Name = client.Name,
+                         TotalCores = clientInfo.NumTotalCores,
+                         UsedCores = offline ? 0 : clientInfo.NumUsedCores,
+                         TotalMemory = clientInfo.TotalMemory,
+                         UsedMemory = offline ? 0 : clientInfo.UsedMemory,
+                         CpuUtilization = offline ? 0 : clientInfo.CpuUtilization,
+                         State = clientInfo.SlaveState.ToString(),
+                       });
+          return new DT.ClientPage {
+            TotalClients = query.Count(),
+            Clients = query.Skip((page - 1) * size)
+              .Take(size)
+              .ToList()
+          };
+        });
+      }
+    }
+
+    public IEnumerable<DT.ClientStatus> GetClientHistory(Guid id, DateTime start, DateTime end) {
+      TimeSpan ts = end - start;
+      int increment = 1;
+      double totalMinutes = ts.TotalMinutes;
+      while (totalMinutes > 5761) {
+        totalMinutes -= 5761;
+        increment += 5;
+      }
+      using (var pm = PersistenceManager) {
+        var factClientInfo = pm.FactClientInfoDao;
+        var clientInfos = factClientInfo.GetByClientId(id)
+          .Where(x => x.Time >= start && x.Time <= end)
+          .OrderBy(x => x.Time)
+          .ToList();
+        var clientStatus = new DT.ClientStatus {
+          CpuUtilization = 0,
+          TotalCores = 0,
+          UsedCores = 0,
+          TotalMemory = 0,
+          UsedMemory = 0
+        };
+        int i = 1;
+        foreach (var clientInfo in clientInfos) {
+          clientStatus.CpuUtilization += clientInfo.CpuUtilization;
+          clientStatus.TotalCores += clientInfo.NumTotalCores;
+          clientStatus.UsedCores += clientInfo.NumUsedCores;
+          clientStatus.TotalMemory += clientInfo.TotalMemory;
+          clientStatus.UsedMemory += clientInfo.UsedMemory;
+          if (i >= increment) {
+            clientStatus.Timestamp = JavascriptUtils.ToTimestamp(clientInfo.Time);
+            clientStatus.CpuUtilization /= i;
+            clientStatus.TotalCores /= i;
+            clientStatus.UsedCores /= i;
+            clientStatus.TotalMemory /= i;
+            clientStatus.UsedMemory /= i;
+            yield return clientStatus;
+            clientStatus = new DT.ClientStatus {
+              CpuUtilization = 0,
+              TotalCores = 0,
+              UsedCores = 0,
+              TotalMemory = 0,
+              UsedMemory = 0
+            };
+            i = 1;
+          } else {
+            ++i;
+          }
+        }
+      }
+    }
+
+  }
+}
Index: anches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataController.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataController.cs	(revision 12515)
+++ 	(revision )
@@ -1,101 +1,0 @@
-﻿#region License Information
-/* HeuristicLab
- * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
- *
- * This file is part of HeuristicLab.
- *
- * HeuristicLab is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * HeuristicLab is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web.Http;
-using HeuristicLab.Services.Access;
-using HeuristicLab.Services.Hive;
-using HeuristicLab.Services.Hive.DataAccess.Interfaces;
-using DTO = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
-
-namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
-
-  [Authorize]
-  public class DataController : ApiController {
-
-    private IPersistenceManager PersistenceManager {
-      get { return ServiceLocator.Instance.PersistenceManager; }
-    }
-
-    private IUserManager UserManager {
-      get { return ServiceLocator.Instance.UserManager; }
-    }
-    private IRoleVerifier RoleVerifier {
-      get { return ServiceLocator.Instance.RoleVerifier; }
-    }
-    private IAuthorizationManager AuthorizationManager {
-      get { return ServiceLocator.Instance.AuthorizationManager; }
-    }
-
-    public IEnumerable<DTO.Job> GetJobs() {
-      return GetJobsByUserId(UserManager.CurrentUserId);
-    }
-
-    public IEnumerable<DTO.Job> GetJobsByUserId(Guid id) {
-      if (id != UserManager.CurrentUserId) {
-        RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
-      }
-      using (var pm = PersistenceManager) {
-        return pm.UseTransaction(() => {
-          var dimJobDao = pm.DimJobDao;
-          return dimJobDao.GetByUserId(id).Select(x => new DTO.Job {
-            Id = x.JobId,
-            Name = x.JobName,
-            DateCreated = x.DateCreated
-          }).ToList();
-        });
-      }
-    }
-
-    public IEnumerable<DTO.Task> GetTasksByJobId(Guid id) {
-      using (var pm = PersistenceManager) {
-        return pm.UseTransaction(() => {
-          var dimJobDao = pm.JobDao;
-          var factTaskDao = pm.FactTaskDao;
-          var job = dimJobDao.GetById(id);
-          if (job != null && job.OwnerUserId != UserManager.CurrentUserId) {
-            RoleVerifier.AuthenticateForAllRoles(HiveRoles.Administrator);
-          }
-          return factTaskDao.GetByJobId(id).Select(x => new DTO.Task {
-            Id = x.TaskId,
-            CalculatingTime = x.CalculatingTime,
-            WaitingTime = x.WaitingTime,
-            TransferTime = x.TransferTime,
-            InitialWaitingTime = x.InitialWaitingTime,
-            NumCalculationRuns = x.NumCalculationRuns,
-            NumRetries = x.NumRetries,
-            CoresRequired = x.CoresRequired,
-            MemoryRequired = x.MemoryRequired,
-            Priority = x.Priority,
-            State = x.TaskState.ToString(),
-            LastClientId = x.LastClientId,
-            StartTime = x.StartTime,
-            EndTime = x.EndTime,
-            Exception = x.Exception
-          }).ToList();
-        });
-      }
-    }
-
-  }
-}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Client.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Client.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Client.cs	(revision 12516)
@@ -26,4 +26,11 @@
     public Guid Id { get; set; }
     public string Name { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+    public string State { get; set; }
+    public DateTime LastUpdate { get; set; }
   }
 }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientDetails.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientDetails.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientDetails.cs	(revision 12516)
@@ -0,0 +1,44 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ClientDetails {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+    public string State { get; set; }
+    public DateTime LastUpdate { get; set; }
+    public long UpTime { get; set; }
+    public long TotalUnavailableTime { get; set; }
+    public long TotalCalculatingTime { get; set; }
+    public long TotalIdleTime { get; set; }
+    public long TotalOfflineTime { get; set; }
+    public long TotalTransferringTime { get; set; }
+    public IEnumerable<TaskStateCount> TasksStates { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientPage.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientPage.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientPage.cs	(revision 12516)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ClientPage {
+    public int TotalClients { get; set; }
+    public IEnumerable<Client> Clients { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientStatus.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientStatus.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/ClientStatus.cs	(revision 12516)
@@ -0,0 +1,32 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class ClientStatus {
+    public long Timestamp { get; set; }
+    public int TotalCores { get; set; }
+    public int UsedCores { get; set; }
+    public int TotalMemory { get; set; }
+    public int UsedMemory { get; set; }
+    public double CpuUtilization { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Integer.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Integer.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Integer.cs	(revision 12516)
@@ -0,0 +1,26 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class Integer {
+    public int Value { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Job.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Job.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Job.cs	(revision 12516)
@@ -26,5 +26,10 @@
     public Guid Id { get; set; }
     public string Name { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
     public DateTime DateCreated { get; set; }
+    public int TotalTasks { get; set; }
+    public int CompletedTasks { get; set; }
+    public DateTime? DateCompleted { get; set; }
   }
 }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobDetails.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobDetails.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobDetails.cs	(revision 12516)
@@ -0,0 +1,44 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class JobDetails {
+    public Guid Id { get; set; }
+    public string Name { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
+    public DateTime DateCreated { get; set; }
+    public int TotalTasks { get; set; }
+    public int CompletedTasks { get; set; }
+    public DateTime? DateCompleted { get; set; }
+    public long AvgTransferringTime { get; set; }
+    public long MinCalculatingTime { get; set; }
+    public long MaxCalculatingTime { get; set; }
+    public long AvgCalculatingTime { get; set; }
+    public long TotalCalculatingTime { get; set; }
+    public long TotalWaitingTime { get; set; }
+    public long TotalTime { get; set; }
+    public IEnumerable<TaskStateCount> TasksStates { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobPage.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobPage.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/JobPage.cs	(revision 12516)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class JobPage {
+    public int TotalJobs { get; set; }
+    public IEnumerable<Job> Jobs { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Task.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Task.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/Task.cs	(revision 12516)
@@ -25,4 +25,7 @@
   public class Task {
     public Guid Id { get; set; }
+    public Guid JobId { get; set; }
+    public string JobName { get; set; }
+    public long TotalTime { get; set; }
     public double CalculatingTime { get; set; }
     public double WaitingTime { get; set; }
@@ -36,4 +39,7 @@
     public string State { get; set; }
     public Guid? LastClientId { get; set; }
+    public string LastClientName { get; set; }
+    public Guid UserId { get; set; }
+    public string UserName { get; set; }
     public DateTime? StartTime { get; set; }
     public DateTime? EndTime { get; set; }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskPage.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskPage.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskPage.cs	(revision 12516)
@@ -0,0 +1,29 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System.Collections.Generic;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class TaskPage {
+    public int TotalTasks { get; set; }
+    public IEnumerable<Task> Tasks { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskStateCount.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskStateCount.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/DataTransfer/TaskStateCount.cs	(revision 12516)
@@ -0,0 +1,27 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer {
+  public class TaskStateCount {
+    public string State { get; set; }
+    public int Count { get; set; }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JavascriptUtils.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JavascriptUtils.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JavascriptUtils.cs	(revision 12516)
@@ -0,0 +1,32 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+
+namespace HeuristicLab.Services.WebApp.Status.WebApi {
+  public static class JavascriptUtils {
+    public static long ToTimestamp(DateTime input) {
+      var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+      var time = input.Subtract(new TimeSpan(epoch.Ticks));
+      return time.Ticks / 10000;
+    }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JobController.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JobController.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/JobController.cs	(revision 12516)
@@ -0,0 +1,150 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Access;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+
+  [Authorize]
+  public class JobController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private IUserManager UserManager {
+      get { return ServiceLocator.Instance.UserManager; }
+    }
+
+    private IRoleVerifier RoleVerifier {
+      get { return ServiceLocator.Instance.RoleVerifier; }
+    }
+
+    public DT.JobDetails GetJobDetails(Guid id) {
+      using (var pm = PersistenceManager) {
+        var dimJobDao = pm.DimJobDao;
+        var factTaskDao = pm.FactTaskDao;
+        return pm.UseTransaction(() => {
+          var job = dimJobDao.GetById(id);
+          if (job != null) {
+            var timeData = factTaskDao.GetByJobId(id).GroupBy(x => x.JobId).Select(x => new {
+              AvgTransferringTime = (long)x.Average(y => y.TransferTime),
+              MinCalculatingTime = (long)x.Min(y => y.CalculatingTime),
+              MaxCalculatingTime = (long)x.Max(y => y.CalculatingTime),
+              AvgCalculatingTime = (long)x.Average(y => y.CalculatingTime),
+              TotalCalculatingTime = (long)x.Sum(y => y.CalculatingTime),
+              TotalWaitingTime = (long)x.Sum(y => y.WaitingTime)
+            }).FirstOrDefault();
+            DateTime endTime = job.DateCompleted ?? DateTime.Now;
+            long totalTime = (long)(endTime - job.DateCreated).TotalSeconds;
+            return new DT.JobDetails {
+              Id = job.JobId,
+              Name = job.JobName,
+              UserId = job.UserId,
+              UserName = job.UserName,
+              DateCreated = job.DateCreated,
+              DateCompleted = job.DateCompleted,
+              TotalTasks = job.TotalTasks,
+              CompletedTasks = job.CompletedTasks,
+              AvgTransferringTime = timeData != null ? timeData.AvgTransferringTime : 0,
+              AvgCalculatingTime = timeData != null ? timeData.AvgCalculatingTime : 0,
+              MinCalculatingTime = timeData != null ? timeData.MinCalculatingTime : 0,
+              MaxCalculatingTime = timeData != null ? timeData.MaxCalculatingTime : 0,
+              TotalCalculatingTime = timeData != null ? timeData.TotalCalculatingTime : 0,
+              TotalWaitingTime = timeData != null ? timeData.TotalWaitingTime : 0,
+              TotalTime = totalTime,
+              TasksStates = factTaskDao.GetByJobId(id)
+                            .GroupBy(x => x.TaskState)
+                            .Select(x => new DT.TaskStateCount {
+                              State = x.Key.ToString(),
+                              Count = x.Count()
+                            }).ToList()
+            };
+          }
+          return null;
+        });
+      }
+    }
+
+    public DT.JobPage GetJobs(int page, int size, bool completed = false) {
+      return GetJobsByUserId(UserManager.CurrentUserId, page, size, completed);
+    }
+
+    public DT.JobPage GetJobsByUserId(Guid id, int page, int size, bool completed = false) {
+      if (id != UserManager.CurrentUserId) {
+        RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      }
+      using (var pm = PersistenceManager) {
+        var dimJobDao = pm.DimJobDao;
+        return pm.UseTransaction(() => {
+          var jobs = dimJobDao.GetByUserId(id).Where(x => completed ? (x.DateCompleted != null) : (x.DateCompleted == null));
+          return new DT.JobPage {
+            TotalJobs = jobs.Count(),
+            Jobs = jobs.OrderByDescending(x => x.DateCompleted ?? DateTime.MaxValue)
+              .Skip((page - 1) * size)
+              .Take(size)
+              .Select(x => ConvertToDT(x))
+              .ToList()
+          };
+        });
+      }
+    }
+
+    public IEnumerable<DT.Job> GetAllJobs(bool completed) {
+      return GetAllJobsByUserId(UserManager.CurrentUserId, completed);
+    }
+
+    public IEnumerable<DT.Job> GetAllJobsByUserId(Guid id, bool completed) {
+      if (id != UserManager.CurrentUserId) {
+        RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+      }
+      using (var pm = PersistenceManager) {
+        var dimJobDao = pm.DimJobDao;
+        return pm.UseTransaction(() => {
+          return dimJobDao.GetByUserId(id)
+            .Where(x => completed ? (x.DateCompleted != null) : (x.DateCompleted == null))
+            .Select(x => ConvertToDT(x))
+            .ToList();
+        });
+      }
+    }
+
+    private DT.Job ConvertToDT(DA.DimJob job) {
+      return new DT.Job {
+        Id = job.JobId,
+        Name = job.JobName,
+        UserId = job.UserId,
+        UserName = job.UserName,
+        DateCreated = job.DateCreated,
+        DateCompleted = job.DateCompleted,
+        TotalTasks = job.TotalTasks,
+        CompletedTasks = job.CompletedTasks
+      };
+    }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/TaskController.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/TaskController.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/TaskController.cs	(revision 12516)
@@ -0,0 +1,154 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Access;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DA = HeuristicLab.Services.Hive.DataAccess;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+  public class TaskController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    private IUserManager UserManager {
+      get { return ServiceLocator.Instance.UserManager; }
+    }
+
+    private IRoleVerifier RoleVerifier {
+      get { return ServiceLocator.Instance.RoleVerifier; }
+    }
+
+    [HttpPost]
+    public DT.TaskPage GetTasksByJobId(Guid id, int page, int size, IEnumerable<string> states) {
+      using (var pm = PersistenceManager) {
+        var dimJobDao = pm.DimJobDao;
+        var dimClientDao = pm.DimClientDao;
+        var factTaskDao = pm.FactTaskDao;
+
+        DA.DimJob job = pm.UseTransaction(() => dimJobDao.GetById(id));
+        if (job == null) {
+          throw new ArgumentException("invalid job id");
+        }
+        if (job.UserId != UserManager.CurrentUserId) {
+          RoleVerifier.AuthenticateForAnyRole(HiveRoles.Administrator);
+        }
+
+        return pm.UseTransaction(() => {
+          var tasks = factTaskDao.GetByJobId(id).Where(x => states.Contains(x.TaskState.ToString()));
+          return new DT.TaskPage {
+            TotalTasks = tasks.Count(),
+            Tasks = (from factTask in tasks
+                     join dimJob in dimJobDao.GetAll() on factTask.JobId equals dimJob.JobId
+                     join dimClient in dimClientDao.GetAll() on factTask.LastClientId equals
+                       dimClient.Id into taskClientJoin
+                     from a in taskClientJoin.DefaultIfEmpty()
+                     let startTime = factTask.StartTime ?? DateTime.Now
+                     let endTime = factTask.EndTime ?? DateTime.Now
+                     select new DT.Task {
+                       Id = factTask.TaskId,
+                       JobId = factTask.JobId,
+                       JobName = dimJob.JobName,
+                       TotalTime = (long)(endTime - startTime).TotalSeconds,
+                       CalculatingTime = factTask.CalculatingTime,
+                       WaitingTime = factTask.WaitingTime,
+                       TransferTime = factTask.TransferTime,
+                       InitialWaitingTime = factTask.InitialWaitingTime,
+                       NumCalculationRuns = factTask.NumCalculationRuns,
+                       NumRetries = factTask.NumRetries,
+                       CoresRequired = factTask.CoresRequired,
+                       MemoryRequired = factTask.MemoryRequired,
+                       Priority = factTask.Priority,
+                       State = factTask.TaskState.ToString(),
+                       LastClientId = factTask.LastClientId,
+                       LastClientName = a != null ? a.Name : string.Empty,
+                       UserId = dimJob.UserId,
+                       UserName = dimJob.UserName,
+                       StartTime = factTask.StartTime,
+                       EndTime = factTask.EndTime,
+                       Exception = factTask.Exception
+                     })
+              .Skip((page - 1) * size)
+              .Take(size)
+              .ToList()
+          };
+        });
+      }
+    }
+
+    [HttpPost]
+    public DT.TaskPage GetTasksByClientId(Guid id, int page, int size, IEnumerable<string> states) {
+      bool isAdministrator = User.IsInRole(HiveRoles.Administrator);
+      using (var pm = PersistenceManager) {
+        var dimJobDao = pm.DimJobDao;
+        var dimClientDao = pm.DimClientDao;
+        var factTaskDao = pm.FactTaskDao;
+
+        return pm.UseTransaction(() => {
+          var tasks = factTaskDao.GetByClientId(id).Where(x => states.Contains(x.TaskState.ToString()));
+          return new DT.TaskPage {
+            TotalTasks = tasks.Count(),
+            Tasks = (from factTask in tasks
+                     join dimJob in dimJobDao.GetAll() on factTask.JobId equals dimJob.JobId
+                     join dimClient in dimClientDao.GetAll() on factTask.LastClientId equals
+                       dimClient.Id into taskClientJoin
+                     from a in taskClientJoin.DefaultIfEmpty()
+                     let startTime = factTask.StartTime ?? DateTime.Now
+                     let endTime = factTask.EndTime ?? DateTime.Now
+                     select new DT.Task {
+                       Id = isAdministrator ? factTask.TaskId : default(Guid),
+                       JobId = isAdministrator ? factTask.JobId : default(Guid),
+                       JobName = isAdministrator ? dimJob.JobName : string.Empty,
+                       TotalTime = (long)(endTime - startTime).TotalSeconds,
+                       CalculatingTime = factTask.CalculatingTime,
+                       WaitingTime = factTask.WaitingTime,
+                       TransferTime = factTask.TransferTime,
+                       InitialWaitingTime = factTask.InitialWaitingTime,
+                       NumCalculationRuns = factTask.NumCalculationRuns,
+                       NumRetries = factTask.NumRetries,
+                       CoresRequired = factTask.CoresRequired,
+                       MemoryRequired = factTask.MemoryRequired,
+                       Priority = factTask.Priority,
+                       State = factTask.TaskState.ToString(),
+                       LastClientId = factTask.LastClientId,
+                       LastClientName = a != null ? a.Name : string.Empty,
+                       UserId = isAdministrator ? dimJob.UserId : default(Guid),
+                       UserName = isAdministrator ? dimJob.UserName : string.Empty,
+                       StartTime = factTask.StartTime,
+                       EndTime = factTask.EndTime,
+                       Exception = isAdministrator ? factTask.Exception : string.Empty
+                     })
+                  .OrderByDescending(x => x.EndTime)
+                  .Skip((page - 1) * size)
+                  .Take(size)
+                  .ToList()
+          };
+        });
+      }
+    }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/UserController.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/UserController.cs	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApi/UserController.cs	(revision 12516)
@@ -0,0 +1,66 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * HeuristicLab is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Http;
+using HeuristicLab.Services.Hive;
+using HeuristicLab.Services.Hive.DataAccess.Interfaces;
+using DT = HeuristicLab.Services.WebApp.Statistics.WebApi.DataTransfer;
+
+namespace HeuristicLab.Services.WebApp.Statistics.WebApi {
+
+  [Authorize(Roles = HiveRoles.Administrator)]
+  public class UserController : ApiController {
+    private IPersistenceManager PersistenceManager {
+      get { return ServiceLocator.Instance.PersistenceManager; }
+    }
+
+    public IEnumerable<DT.User> GetUsers() {
+      using (var pm = PersistenceManager) {
+        var dimUserDao = pm.DimUserDao;
+        return pm.UseTransaction(() => {
+          return dimUserDao.GetAll().Select(x => new DT.User {
+            Id = x.UserId,
+            Name = x.Name
+          }).ToList();
+        });
+      }
+    }
+
+    public DT.User GetUser(Guid id) {
+      using (var pm = PersistenceManager) {
+        var dimUserDao = pm.DimUserDao;
+        return pm.UseTransaction(() => {
+          var user = dimUserDao.GetById(id);
+          if (user != null) {
+            return new DT.User {
+              Id = user.UserId,
+              Name = user.Name
+            };
+          }
+          return null;
+        });
+      }
+    }
+  }
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clients.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clients.cshtml	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clients.cshtml	(revision 12516)
@@ -1,21 +1,102 @@
 ﻿@using HeuristicLab.Services.Access.DataTransfer
 @using HeuristicLab.Services.Hive
-@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
 <header class="view-header">
     <ul class="nav nav-list nav-list-topbar pull-left">
-        <li>
-            <a ng-href="#/statistics/jobs">Jobs</a>
-        </li>
+    <li>
+        <a ng-href="#/statistics/jobs">Jobs</a>
+    </li>
+    @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator))
+    {
         <li>
             <a ng-href="#/statistics/users">Users</a>
         </li>
-        <li class="active">
-            <a ng-href="#/statistics/clients">Clients</a>
-        </li>
+    }
+    <li class="active">
+        <a ng-href="#/statistics/clients">Clients</a>
+    </li>
     </ul>
 </header>
 
 <div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Current Clients</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                        <tr>
+                            <th>#</th>
+                            <th>Client Name</th>
+                            <th>Cores</th>
+                            <th>Cpu Utilization</th>
+                            <th>State</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        <tr ng-repeat="client in clientPage.Clients">
+                            <td>{{($index + 1)+((curClientsPage-1)*(clientsPageSize))}}</td>
+                            <td>{{client.Name}}</td>
+                            <td>{{client.UsedCores}} / {{client.TotalCores}}</td>
+                            <td>{{client.CpuUtilization | number: 2}} %</td>
+                            <td>{{client.State}}</td>
+                            <td>
+                                <a ng-href="#/statistics/clients/{{client.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="clientPage.Clients.length">
+                            <td colspan="6" class="text-center">No clients found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="clientPage.TotalClients > clientPage.Clients.length">
+                        <pagination max-size="10" total-items="clientPage.TotalClients" ng-model="curClientsPage" ng-change="changeClientsPage()" items-per-page="clientsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
 
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Expired Clients</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Client Name</th>
+                                <th>Cores</th>
+                                <th>Cpu Utilization</th>
+                                <th>State</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="client in expiredClientPage.Clients">
+                            <td>{{($index + 1)+((curExpiredClientsPage-1)*(expiredClientsPageSize))}}</td>
+                            <td>{{client.Name}}</td>
+                            <td>{{client.UsedCores}} / {{client.TotalCores}}</td>
+                            <td>{{client.CpuUtilization | number: 2}} %</td>
+                            <td>{{client.State}}</td>
+                            <td>
+                                <a ng-href="#/statistics/clients/{{client.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="expiredClientPage.Clients.length">
+                            <td colspan="6" class="text-center">No expired clients found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="expiredClientPage.TotalClients > expiredClientPage.Clients.length">
+                        <pagination max-size="10" total-items="expiredClientPage.TotalClients" ng-model="curExpiredClientsPage" ng-change="changeExpiredClientsPage()" items-per-page="expiredClientsPageSize"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
 </div>
-}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clientsCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clientsCtrl.js	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/clientsCtrl.js	(revision 12516)
@@ -2,6 +2,48 @@
     var module = appStatisticsPlugin.getAngularModule();
     module.controller('app.statistics.clientsCtrl',
-        ['$scope', 'app.statistics.data.service', function($scope, statisticsService) {
-            
+        ['$scope', '$interval', 'app.statistics.clientService',
+        function ($scope, $interval, clientService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curClientsPage = 1;
+            $scope.clientsPageSize = 20;
+            $scope.curExpiredClientsPage = 1;
+            $scope.expiredClientsPageSize = 20;
+
+            var getClients = function () {
+                clientService.getClients({ page: $scope.curClientsPage, size: $scope.clientsPageSize, expired: false },
+                    function (clientPage) {
+                        $scope.clientPage = clientPage;
+                    }
+                );
+            };
+
+            var getExpiredClients = function() {
+                clientService.getClients({ page: $scope.curExpiredClientsPage, size: $scope.expiredClientsPageSize, expired: true },
+                    function (clientPage) {
+                        $scope.expiredClientPage = clientPage;
+                    }
+                );
+            };
+
+            var update = function () {
+                getClients();
+                getExpiredClients();
+            };
+
+            $scope.changeClientsPage = function () {
+                update();
+            };
+
+            $scope.changeExpiredClientsPage = function () {
+                update();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+
         }]
     );
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetails.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetails.cshtml	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetails.cshtml	(revision 12516)
@@ -0,0 +1,320 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li class="active">
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Client Details</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="row">
+                        <div class="col-md-6">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Name:</td>
+                                    <td>{{client.Name}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Group:</td>
+                                    <td ng-show="client.GroupName">{{client.GroupName}}</td>
+                                    <td ng-hide="client.GroupName">None</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">State:</td>
+                                    <td>{{client.State}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Up Time:</td>
+                                    <td>{{client.UpTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Last Update:</td>
+                                    <td>{{client.LastUpdate}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        <div class="col-md-6">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Total Unavailable Time:</td>
+                                    <td class="text-right">{{client.TotalUnavailableTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Calculating Time:</td>
+                                    <td class="text-right">{{client.TotalCalculatingTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Idle Time:</td>
+                                    <td class="text-right">{{client.TotalIdleTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Transferring Time:</td>
+                                    <td class="text-right">{{client.TotalTransferringTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Offline Time:</td>
+                                    <td class="text-right">{{client.TotalOfflineTime}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalClientTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in states">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Cores</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.cores" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{client.TotalCores | number}}</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Used:</td>
+                            <td class="text-right">{{client.UsedCores | number}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">CPU Utilization</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.cpu" knob-options="knobOptions"></knob>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Memory</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="text-center">
+                        <knob knob-data="knobData.memory" knob-options="knobOptions"></knob>
+                    </div>
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{client.TotalMemory | number}} GB</td>
+                        </tr>
+                        <tr>
+                            <td class="text-left">Used:</td>
+                            <td class="text-right">{{client.UsedMemory | number}} GB</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+    </div>
+</div>
+
+<div id="tasks-filter" class="default-filter-header text-center">
+    <form class="form-inline">
+        <div class="form-group">
+            <div class="btn-group" data-toggle="buttons">
+                <label class="btn btn-default" ng-class="{active: state.Selected}" ng-repeat="state in states"
+                       ng-click="filterTaskState(state)">
+                    <input type="checkbox" autocomplete="off" checked> {{state.State}}
+                </label>
+            </div>
+        </div>
+    </form>
+</div>
+
+ <div class="default-view-container">
+     <div id="taskListContainer" class="row" ng-show="taskPage.Tasks.length">
+         <div class="col-lg-4 col-md-4" ng-repeat="task in taskPage.Tasks">
+             <div class="panel panel-default">
+                 @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                    <div class="panel-heading cursor-pointer" ng-click="openDialog(($index + 1)+((curTaskPage-1)*(taskPageSize)), task)">
+                        <h3 class="panel-title" style="display: inline">Task {{($index + 1)+((curTaskPage-1)*(taskPageSize))}}</h3>
+                        <span ng-if="task.State == 'Finished'" class="label label-success" style="float: right;">{{task.State}}</span>
+                        <span ng-if="task.State == 'Aborted' || task.State == 'Failed'" class="label label-danger" style="float: right;">{{task.State}}</span>
+                        <span ng-if="task.State == 'Waiting'" class="label label-warning" style="float: right;">{{task.State}}</span>
+                        <span ng-if="task.State != 'Waiting' && task.State != 'Failed' && task.State != 'Aborted' && task.State != 'Finished'" class="label label-default" style="float: right;">{{task.State}}</span>
+                    </div>
+                 } else {
+                    <div class="panel-heading">
+                        <h3 class="panel-title" style="display: inline">Task {{($index + 1)+((curTaskPage-1)*(taskPageSize))}}</h3>
+                        <span ng-if="task.State == 'Finished'" class="label label-success" style="float: right;">{{task.State}}</span>
+                        <span ng-if="task.State == 'Aborted' || task.State == 'Failed'" class="label label-danger" style="float: right;">{{task.State}}</span>
+                        <span ng-if="task.State == 'Waiting'" class="label label-warning" style="float: right;">{{task.State}}</span>
+                        <span ng-if="task.State != 'Waiting' && task.State != 'Failed' && task.State != 'Aborted' && task.State != 'Finished'" class="label label-default" style="float: right;">{{task.State}}</span>
+                    </div>
+                 }
+                 
+                 <div class="panel-body">
+                     <table class="table table-condensed table-no-border table-auto-width">
+                         @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator))
+                         {
+                             <tr>
+                                 <td colspan="2"><span data-fittext="0.99">{{task.Id}}</span></td>
+                             </tr>
+                         }
+                         <tr>
+                             <td>Start Time:</td>
+                             <td class="text-right">{{task.StartTime}}</td>
+                         </tr>
+                         <tr>
+                             <td>End Time:</td>
+                             <td class="text-right">{{task.EndTime}}</td>
+                         </tr>
+                         <tr>
+                             <td>Calculating Time:</td>
+                             <td class="text-right">{{task.CalculatingTime}}</td>
+                         </tr>
+                         @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator))
+                         {
+                             <tr>
+                                 <td>User:</td>
+                                 <td class="text-right">
+                                     <a ng-href="#/statistics/users/{{task.UserId}}" ng-show="task.UserName">{{task.UserName}}</a>
+                                     <span ng-hide="task.UserName">Unknown</span>
+                                 </td>
+                             </tr>
+                             <tr>
+                                 <td>Job:</td>
+                                 <td class="text-right">
+                                     <a ng-href="#/statistics/jobs/{{task.JobId}}" ng-show="task.JobName">{{task.JobName}}</a>
+                                     <span ng-hide="task.JobName">Unknown</span>
+                                 </td>
+                             </tr>
+                         }
+                     </table>
+                 </div>
+
+             </div>
+         </div>
+     </div>
+     <div ng-hide="taskPage.Tasks.length">
+         <p class="text-center">Please select at least one state!</p>
+     </div>
+     <div class="row text-center" ng-show="taskPage.TotalTasks > taskPage.Tasks.length">
+         <pagination max-size="10" total-items="taskPage.TotalTasks" ng-model="curTaskPage" ng-change="changeTaskPage()" items-per-page="taskPageSize"
+                     boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+     </div>
+ </div>
+
+ <div class="default-filter-header text-center">
+     <form class="form-inline">
+         <div class="form-group">
+             <label for="fromDate">From:&nbsp;</label>
+             <div class="input-group">
+                 <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="fromDate" is-open="fromIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
+                 <span class="input-group-btn">
+                     <button type="button" class="btn btn-default" ng-click="openFromDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                 </span>
+             </div>
+         </div>
+         <div class="form-group">
+             <label for="fromDate">&nbsp;&nbsp;To:&nbsp;</label>
+             <div class="input-group">
+                 <input id="fromDate" type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="toDate" is-open="toIsOpen" datepicker-options="dateOptions" ng-required="true" close-text="Close"/>
+                 <span class="input-group-btn">
+                     <button type="button" class="btn btn-default" ng-click="openToDateSelection($event)"><i class="glyphicon glyphicon-calendar"></i></button>
+                 </span>
+             </div>
+         </div>
+         <div class="btn-group">
+             <label class="btn btn-default" ng-click="updateCharts()">
+                 Apply
+             </label>
+         </div>
+     </form>
+ </div>
+
+ <div class="default-view-container">
+     <div class="row">
+         <div class="col-lg-12">
+             <div class="panel panel-default">
+                 <div class="panel-heading">
+                     <h3 class="panel-title">CPU Utilization Chart</h3>
+                 </div>
+                 <div class="panel-body">
+                     <flot dataset="cpuSeries" options="chartOptions"></flot>
+                 </div>
+             </div>
+         </div>
+     </div>
+
+     <div class="row">
+         <div class="col-lg-12">
+             <div class="panel panel-default">
+                 <div class="panel-heading">
+                     <h3 class="panel-title">Core Chart</h3>
+                 </div>
+                 <div class="panel-body">
+                     <flot dataset="coreSeries" options="fillChartOptions"></flot>
+                 </div>
+             </div>
+         </div>
+     </div>
+
+     <div class="row">
+         <div class="col-lg-12">
+             <div class="panel panel-default">
+                 <div class="panel-heading">
+                     <h3 class="panel-title">Memory Chart</h3>
+                 </div>
+                 <div class="panel-body">
+                     <flot dataset="memorySeries" options="fillChartOptions"></flot>
+                 </div>
+             </div>
+         </div>
+     </div>
+ </div>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetailsCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetailsCtrl.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientDetailsCtrl.js	(revision 12516)
@@ -0,0 +1,257 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.clientDetailsCtrl',
+        ['$scope', '$stateParams', '$interval', 'app.statistics.clientService', 'app.statistics.taskService', '$modal',
+        function ($scope, $stateParams, $interval, clientService, taskService, $modal) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curTaskPage = 1;
+            $scope.taskPageSize = 12;
+
+            // details
+            $scope.knobOptions = {
+                'fgColor': "#f7921d",
+                'angleOffset': -125,
+                'angleArc': 250,
+                'readOnly': true,
+                'width': "80%",
+                'targetvalue': "100",
+                'format': function (value) {
+                    return value;
+                },
+                draw: function () {
+                    $(this.i).val(this.cv + '%');
+                }
+            };
+
+            $scope.knobData = {
+                cores: 0,
+                cpu: 0,
+                memory: 0
+            };
+
+            var getClientDetails = function () {
+                clientService.getClientDetails({ id: $stateParams.id }, function (client) {
+                    $scope.client = client;
+                    $scope.client.TotalMemory = Math.round($scope.client.TotalMemory / 1024);
+                    $scope.client.UsedMemory = Math.round($scope.client.UsedMemory / 1024);
+                    $scope.knobData.cores = (client.UsedCores / client.TotalCores) * 100;
+                    $scope.knobData.cpu = client.CpuUtilization;
+                    $scope.knobData.memory = (client.UsedMemory / client.TotalMemory) * 100;
+                    $scope.client.LastUpdate = CSharpDateToString($scope.client.LastUpdate);
+                    $scope.client.UpTime = $scope.client.UpTime.toHHMMSS();
+                    $scope.client.TotalUnavailableTime = $scope.client.TotalUnavailableTime.toHHMMSS();
+                    $scope.client.TotalCalculatingTime = $scope.client.TotalCalculatingTime.toHHMMSS();
+                    $scope.client.TotalIdleTime = $scope.client.TotalIdleTime.toHHMMSS();
+                    $scope.client.TotalOfflineTime = $scope.client.TotalOfflineTime.toHHMMSS();
+                    $scope.client.TotalTransferringTime = $scope.client.TotalTransferringTime.toHHMMSS();
+
+                    var length = client.TasksStates.length;
+                    var total = 0;
+                    var jsStates = [];
+                    for (var i = 0; i < length; ++i) {
+                        var state = client.TasksStates[i];
+                        var selected = true;
+                        if (isDefined($scope.states)) {
+                            for (var j = 0; j < $scope.states.length; ++j) {
+                                if (state.State == $scope.states[j].State) {
+                                    selected = $scope.states[j].Selected;
+                                    break;
+                                }
+                            }
+                        }
+                        jsStates.push({
+                            State: state.State,
+                            Count: state.Count,
+                            Selected: selected
+                        });
+                        total += state.Count;
+                    }
+                    $scope.totalClientTasks = total;
+                    $scope.states = jsStates;
+                    getTasks();
+                });
+            };
+
+            // tasks
+            var getTasks = function () {
+                var states = [];
+                var length = $scope.states.length;
+                for (var i = 0; i < length; ++i) {
+                    var state = $scope.states[i];
+                    if (state.Selected) {
+                        states.push(state.State);
+                    }
+                }
+
+                taskService.getTasksByClientId({ id: $stateParams.id, page: $scope.curTaskPage, size: $scope.taskPageSize }, states,
+                    function (taskPage) {
+                        $scope.taskPage = taskPage;
+                        var length = $scope.taskPage.Tasks.length;
+                        for (var i = 0; i < length; ++i) {
+                            $scope.taskPage.Tasks[i].TotalTime = $scope.taskPage.Tasks[i].TotalTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].InitialWaitingTime = $scope.taskPage.Tasks[i].InitialWaitingTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].WaitingTime = $scope.taskPage.Tasks[i].WaitingTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].CalculatingTime = $scope.taskPage.Tasks[i].CalculatingTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].TransferTime = $scope.taskPage.Tasks[i].TransferTime.toHHMMSS();
+                            var startTime = $scope.taskPage.Tasks[i].StartTime;
+                            if (isDefined(startTime)) {
+                                $scope.taskPage.Tasks[i].StartTime = CSharpDateToString(startTime);
+                            } else {
+                                $scope.taskPage.Tasks[i].StartTime = 'Not started';
+                            }
+                            var endTime = $scope.taskPage.Tasks[i].EndTime;
+                            if (isDefined(endTime)) {
+                                $scope.taskPage.Tasks[i].EndTime = CSharpDateToString(endTime);
+                            } else {
+                                $scope.taskPage.Tasks[i].EndTime = 'Not finished';
+                            }
+                        }
+                    }
+                );
+            };
+
+            $scope.changeTaskPage = function () {
+                getClientDetails();
+                $('html, body').animate({
+                    scrollTop: $("#tasks-filter").offset().top
+                }, 10);
+            };
+
+            $scope.filterTaskState = function (state) {
+                state.Selected = !state.Selected;
+                $scope.curTaskPage = 1;
+                getClientDetails();
+            };
+
+            $scope.openDialog = function (taskNo, task) {
+                $scope.currentTaskNo = taskNo;
+                $scope.currentTask = task;
+                $modal.open({
+                    templateUrl: 'plugin=statistics&view=WebApp/clients/details/clientTaskDetailsDialog.cshtml',
+                    controller: 'app.statistics.clientTaskDetailsDialogCtrl',
+                    windowClass: 'app-modal-window',
+                    resolve: {
+                        task: function () {
+                            return $scope.currentTask;
+                        },
+                        taskNo: function () {
+                            return $scope.currentTaskNo;
+                        }
+                    }
+                });
+            };
+
+            // charts
+            $scope.chartOptions = {
+                grid: {
+                    borderWidth: 1,
+                    labelMargin: 15
+                },
+                series: {
+                    shadowSize: 0
+                },
+                yaxis: {
+                    min: 0,
+                    max: 100,
+                    zoomRange: false,
+                    panRange: false
+                },
+                xaxis: {
+                    mode: "time",
+                    twelveHourClock: false
+                }
+            };
+
+            $scope.fillChartOptions = {
+                grid: {
+                    borderWidth: 1,
+                    labelMargin: 15
+                },
+                series: {
+                    shadowSize: 0,
+                    lines: {
+                        show: true,
+                        fill: true
+                    }
+                },
+                yaxis: {
+                    zoomRange: false,
+                    panRange: false
+                },
+                xaxis: {
+                    mode: "time",
+                    twelveHourClock: false
+                }
+            };
+
+            $scope.fromDate = new Date();
+            $scope.toDate = new Date();
+
+            $scope.fromIsOpen = false;
+            $scope.toIsOpen = false;
+
+            $scope.openFromDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.toIsOpen = false;
+                $scope.fromIsOpen = true;
+            };
+
+            $scope.openToDateSelection = function ($event) {
+                $event.preventDefault();
+                $event.stopPropagation();
+                $scope.fromIsOpen = false;
+                $scope.toIsOpen = true;
+            };
+
+            $scope.dateOptions = {
+                formatYear: 'yy',
+                startingDay: 1
+            };
+
+            $scope.cpuSeries = [[]];
+            $scope.coreSeries = [[]];
+            $scope.memorySeries = [[]];
+
+            $scope.updateCharts = function () {
+                clientService.getClientHistory({ id: $stateParams.id, start: ConvertFromDate($scope.fromDate), end: ConvertToDate($scope.toDate) }, function (status) {
+                    var noOfStatus = status.length;
+                    var cpuSeries = [];
+                    var coreSeries = [[], []];
+                    var memorySeries = [[], []];
+                    for (var i = 0; i < noOfStatus; ++i) {
+                        var curStatus = status[i];
+                        var cpuData = Math.round(curStatus.CpuUtilization);
+                        cpuSeries.push([curStatus.Timestamp, cpuData]);
+                        coreSeries[0].push([curStatus.Timestamp, curStatus.TotalCores]);
+                        coreSeries[1].push([curStatus.Timestamp, curStatus.UsedCores]);
+                        memorySeries[0].push([curStatus.Timestamp, curStatus.TotalMemory]);
+                        memorySeries[1].push([curStatus.Timestamp, curStatus.UsedMemory]);
+                    }
+                    $scope.cpuSeries = [{ data: cpuSeries, label: "&nbsp;CPU Utilization", color: "#f7921d" }];
+                    $scope.coreSeries = [
+                        { data: coreSeries[0], label: "&nbsp;Total Cores", color: "LightGreen" },
+                        { data: coreSeries[1], label: "&nbsp;Used Cores", color: "LightPink" }
+                    ];
+                    $scope.memorySeries = [
+                        { data: memorySeries[0], label: "&nbsp;Total Memory", color: "LightGreen" },
+                        { data: memorySeries[1], label: "&nbsp;Used Memory", color: "LightPink" }
+                    ];
+
+                });
+            };
+
+            var update = function () {
+                getClientDetails();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+            $scope.updateCharts();
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialog.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialog.cshtml	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialog.cshtml	(revision 12516)
@@ -0,0 +1,114 @@
+﻿@using HeuristicLab.Services.Hive
+@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+<div class="modal-header">
+    <button type="button" ng-click="close()" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+    <h4 class="modal-title">Task #{{taskNo}} - {{task.Id}}</h4>
+</div>
+<div class="modal-body" style="height: 430px">
+    <tabset>
+        <tab heading="Details">
+            <div style="height: 330px" class="center-block">
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Start Time:</td>
+                                <td class="text-right">{{task.StartTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>End Time:</td>
+                                <td class="text-right">{{task.EndTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>State:</td>
+                                <td>{{task.State}}</td>
+                            </tr>
+                        </table>
+                    </div>
+
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Slave:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/clients/{{task.LastClientId}}" ng-show="task.LastClientName">{{task.LastClientName}}</a>
+                                    <span ng-hide="task.LastClientName">None</span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>User:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/users/{{task.UserId}}" ng-show="task.UserName">{{task.UserName}}</a>
+                                    <span ng-hide="task.UserName">Unknown</span>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>Job:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/jobs/{{task.JobId}}" ng-show="task.JobName">{{task.JobName}}</a>
+                                    <span ng-hide="task.JobName">Unknown</span>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Total Time:</td>
+                                <td>{{task.TotalTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Initial Waiting Time:</td>
+                                <td>{{task.InitialWaitingTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Waiting Time:</td>
+                                <td>{{task.WaitingTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Calculating Time:</td>
+                                <td>{{task.CalculatingTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Transfer Time:</td>
+                                <td>{{task.TransferTime}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Calculation Runs:</td>
+                                <td>{{task.NumCalculationRuns}}</td>
+                            </tr>
+                            <tr>
+                                <td>Retries Runs:</td>
+                                <td>{{task.NumRetries}}</td>
+                            </tr>
+                            <tr>
+                                <td>Cores Required:</td>
+                                <td>{{task.CoresRequired}}</td>
+                            </tr>
+                            <tr>
+                                <td>Memory Required:</td>
+                                <td>{{task.MemoryRequired}}</td>
+                            </tr>
+                            <tr>
+                                <td>Priority:</td>
+                                <td>{{task.Priority}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </tab>
+        <tab heading="Exception" ng-show="task.Exception">
+            <div style="overflow: auto;">
+                <p style="word-wrap: break-word;">{{task.Exception}}</p>
+            </div>
+        </tab>
+    </tabset>
+</div>
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialogCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialogCtrl.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/clients/details/clientTaskDetailsDialogCtrl.js	(revision 12516)
@@ -0,0 +1,14 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.clientTaskDetailsDialogCtrl',
+        ['$scope', '$modalInstance', 'taskNo', 'task', function ($scope, $modalInstance, taskNo, task) {
+            $scope.taskNo = taskNo;
+            $scope.task = task;
+
+            $scope.close = function () {
+                $modalInstance.dismiss('cancel');
+            };
+
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetails.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetails.cshtml	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetails.cshtml	(revision 12516)
@@ -0,0 +1,187 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li class="active">
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+            <li>
+                <a ng-href="#/statistics/users">Users</a>
+            </li>
+        }
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9 col-md-9">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Job Details</h3>
+                </div>
+                <div class="panel-body">
+                    <div class="row">
+                        <div class="col-md-12">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Name:</td>
+                                    <td>{{job.Name}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Created at:</td>
+                                    <td>{{job.DateCreated}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Completed at:</td>
+                                    <td>{{job.DateCompleted}}</td>
+                                </tr>
+                                @if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+                                    <tr>
+                                        <td class="text-left">User:</td>
+                                        <td>
+                                            <a ng-href="#/statistics/users/{{job.UserId}}" ng-show="job.UserName">{{job.UserName}}</a>
+                                            <span ng-hide="job.UserName">None</span>
+                                        </td>
+                                    </tr>
+                                }
+                            </table>
+                        </div>
+                    </div>
+                    <div class="row" style="height: 20px"></div>
+                    <div class="row">
+                        <div class="col-md-5">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Total Time:</td>
+                                    <td class="text-right">{{job.TotalTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Calculating Time:</td>
+                                    <td class="text-right">{{job.TotalCalculatingTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Total Waiting Time:</td>
+                                    <td class="text-right">{{job.TotalWaitingTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Calc. / Waiting Ratio:</td>
+                                    <td class="text-right">{{job.CalculatingWaitingRatio | number: 3}}</td>
+                                </tr>
+                            </table>
+                        </div>
+                        <div class="col-md-5">
+                            <table class="table table-no-border table-condensed table-auto-width table-content">
+                                <tr>
+                                    <td class="text-left">Min Calculating Time:</td>
+                                    <td class="text-right">{{job.MinCalculatingTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Max Calculating Time:</td>
+                                    <td class="text-right">{{job.MaxCalculatingTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Avg. Calculating Time:</td>
+                                    <td class="text-right">{{job.AvgCalculatingTime}}</td>
+                                </tr>
+                                <tr>
+                                    <td class="text-left">Avg. Transferring Time:</td>
+                                    <td class="text-right">{{job.AvgTransferringTime}}</td>
+                                </tr>
+                            </table>
+                        </div>
+
+                        <div class="col-md-2"></div>
+                        </div>
+                </div>
+
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-3">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalJobTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in states">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div id="job-filter-header" class="text-center">
+    <form class="form-inline">
+        <div class="form-group">
+            <div class="btn-group" data-toggle="buttons">
+                <label class="btn btn-default" ng-class="{active: state.Selected}" ng-repeat="state in states"
+                       ng-click="filterState(state)">
+                    <input type="checkbox" autocomplete="off" checked> {{state.State}}
+                </label>
+            </div>
+        </div>
+    </form>
+</div>
+
+<div class="default-view-container">
+    <div id="taskListContainer" class="row" ng-show="taskPage.Tasks.length">
+        <div class="col-lg-4 col-md-4" ng-repeat="task in taskPage.Tasks">
+            <div class="panel panel-default">
+                <div class="panel-heading cursor-pointer" ng-click="openDialog(($index + 1)+((curTaskPage-1)*(taskPageSize)), task)">
+                    <h3 class="panel-title" style="display: inline">Task {{($index + 1)+((curTaskPage-1)*(taskPageSize))}}</h3>
+                    <span ng-if="task.State == 'Finished'" class="label label-success" style="float: right;">{{task.State}}</span>
+                    <span ng-if="task.State == 'Aborted' || task.State == 'Failed'" class="label label-danger" style="float: right;">{{task.State}}</span>
+                    <span ng-if="task.State == 'Waiting'" class="label label-warning" style="float: right;">{{task.State}}</span>
+                    <span ng-if="task.State != 'Waiting' && task.State != 'Failed' && task.State != 'Aborted' && task.State != 'Finished'" class="label label-default" style="float: right;">{{task.State}}</span>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-condensed table-no-border table-auto-width">
+                        <tr>
+                            <td colspan="2"><span data-fittext="0.99">{{task.Id}}</span></td>
+                        </tr>
+                        <tr>
+                            <td>Start Time:</td>
+                            <td class="text-right">{{task.StartTime}}</td>
+                        </tr>
+                        <tr>
+                            <td>End Time:</td>
+                            <td class="text-right">{{task.EndTime}}</td>
+                        </tr>
+                        <tr>
+                            <td>Calculating Time:</td>
+                            <td class="text-right">{{task.CalculatingTime}}</td>
+                        </tr>
+                        <tr>
+                            <td>Slave:</td>
+                            <td class="text-right">
+                                <a ng-href="#/statistics/clients/{{task.LastClientId}}" ng-show="task.LastClientName">{{task.LastClientName}}</a>
+                                <span ng-hide="task.LastClientName">None</span>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+
+            </div>
+        </div>
+    </div>
+    <div ng-hide="taskPage.Tasks.length">
+        <p class="text-center">Please select at least one state!</p>
+    </div>
+    <div class="row text-center" ng-show="taskPage.TotalTasks > taskPage.Tasks.length">
+        <pagination max-size="10" total-items="taskPage.TotalTasks" ng-model="curTaskPage" ng-change="changeTaskPage()" items-per-page="taskPageSize"
+                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+    </div>
+</div>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetailsCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetailsCtrl.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobDetailsCtrl.js	(revision 12516)
@@ -0,0 +1,133 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.jobDetailsCtrl',
+        ['$scope', '$interval', '$stateParams', 'app.statistics.jobService', 'app.statistics.taskService', '$modal',
+        function ($scope, $interval, $stateParams, jobService, taskService, $modal) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.curTaskPage = 1;
+            $scope.taskPageSize = 12;
+
+            var getJobDetails = function () {
+                jobService.getJobDetails({ id: $stateParams.id }, function (job) {
+                    $scope.job = job;
+                    $scope.job.DateCreated = CSharpDateToString($scope.job.DateCreated);
+                    if (isDefined($scope.job.DateCompleted)) {
+                        $scope.job.DateCompleted = CSharpDateToString($scope.job.DateCompleted);
+                    }
+                    $scope.job.CalculatingWaitingRatio = ($scope.job.TotalCalculatingTime / $scope.job.TotalWaitingTime);
+                    $scope.job.AvgTransferringTime = $scope.job.AvgTransferringTime.toHHMMSS();
+                    $scope.job.AvgCalculatingTime = $scope.job.AvgCalculatingTime.toHHMMSS();
+                    $scope.job.MinCalculatingTime = $scope.job.MinCalculatingTime.toHHMMSS();
+                    $scope.job.MaxCalculatingTime = $scope.job.MaxCalculatingTime.toHHMMSS();
+                    $scope.job.TotalCalculatingTime = $scope.job.TotalCalculatingTime.toHHMMSS();
+                    $scope.job.TotalWaitingTime = $scope.job.TotalWaitingTime.toHHMMSS();
+                    $scope.job.TotalTime = $scope.job.TotalTime.toHHMMSS();
+
+                    var length = job.TasksStates.length;
+                    var total = 0;
+                    var jsStates = [];
+                    for (var i = 0; i < length; ++i) {
+                        var state = job.TasksStates[i];
+                        var selected = true;
+                        if (isDefined($scope.states)) {
+                            for (var j = 0; j < $scope.states.length; ++j) {
+                                if (state.State == $scope.states[j].State) {
+                                    selected = $scope.states[j].Selected;
+                                    break;
+                                }
+                            }
+                        }
+                        jsStates.push({
+                            State: state.State,
+                            Count: state.Count,
+                            Selected: selected
+                        });
+                        total += state.Count;
+                    }
+                    $scope.totalJobTasks = total;
+                    $scope.states = jsStates;
+                    getTasks();
+                });
+            };
+
+            var getTasks = function () {
+                var states = [];
+                var length = $scope.states.length;
+                for (var i = 0; i < length; ++i) {
+                    var state = $scope.states[i];
+                    if (state.Selected) {
+                        states.push(state.State);
+                    }
+                }
+
+                taskService.getTasksByJobId({ id: $stateParams.id, page: $scope.curTaskPage, size: $scope.taskPageSize }, states,
+                    function (taskPage) {
+                        $scope.taskPage = taskPage;
+                        var length = $scope.taskPage.Tasks.length;
+                        for (var i = 0; i < length; ++i) {
+                            $scope.taskPage.Tasks[i].TotalTime = $scope.taskPage.Tasks[i].TotalTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].InitialWaitingTime = $scope.taskPage.Tasks[i].InitialWaitingTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].WaitingTime = $scope.taskPage.Tasks[i].WaitingTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].CalculatingTime = $scope.taskPage.Tasks[i].CalculatingTime.toHHMMSS();
+                            $scope.taskPage.Tasks[i].TransferTime = $scope.taskPage.Tasks[i].TransferTime.toHHMMSS();
+                            var startTime = $scope.taskPage.Tasks[i].StartTime;
+                            if (isDefined(startTime)) {
+                                $scope.taskPage.Tasks[i].StartTime = CSharpDateToString(startTime);
+                            } else {
+                                $scope.taskPage.Tasks[i].StartTime = 'Not started';
+                            }
+                            var endTime = $scope.taskPage.Tasks[i].EndTime;
+                            if (isDefined(endTime)) {
+                                $scope.taskPage.Tasks[i].EndTime = CSharpDateToString(endTime);
+                            } else {
+                                $scope.taskPage.Tasks[i].EndTime = 'Not finished';
+                            }
+                        }
+                    }
+                );
+            };
+
+            $scope.changeTaskPage = function () {
+                getJobDetails();
+                $('html, body').animate({
+                    scrollTop: $("#job-filter-header").offset().top
+                }, 10);
+            };
+
+            $scope.filterState = function (state) {
+                state.Selected = !state.Selected;
+                $scope.curTaskPage = 1;
+                getJobDetails();
+            };
+
+            $scope.openDialog = function (taskNo, task) {
+                $scope.currentTaskNo = taskNo;
+                $scope.currentTask = task;
+                $modal.open({
+                    templateUrl: 'plugin=statistics&view=WebApp/jobs/details/jobTaskDetailsDialog.cshtml',
+                    controller: 'app.statistics.jobTaskDetailsDialogCtrl',
+                    windowClass: 'app-modal-window',
+                    resolve: {
+                        task: function() {
+                            return $scope.currentTask;
+                        },
+                        taskNo: function() {
+                            return $scope.currentTaskNo;
+                        }
+                    }
+                });
+            };
+
+            var update = function () {
+                getJobDetails();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialog.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialog.cshtml	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialog.cshtml	(revision 12516)
@@ -0,0 +1,92 @@
+﻿<div class="modal-header">
+    <button type="button" ng-click="close()" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+    <h4 class="modal-title">Task #{{taskNo}} - {{task.Id}}</h4>
+</div>
+<div class="modal-body" style="height: 430px">
+    <tabset>
+        <tab heading="Details">
+            <div style="height: 330px" class="center-block">
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-12">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Start Time:</td>
+                                <td class="text-right">{{task.StartTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>End Time:</td>
+                                <td class="text-right">{{task.EndTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>State:</td>
+                                <td>{{task.State}}</td>
+                            </tr>
+                            <tr>
+                                <td>Slave:</td>
+                                <td>
+                                    <a ng-click="close()" ng-href="#/statistics/clients/{{task.LastClientId}}" ng-show="task.LastClientName">{{task.LastClientName}}</a>
+                                    <span ng-hide="task.LastClientName">None</span>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+                <div class="row" style="padding-top: 30px">
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Total Time:</td>
+                                <td>{{task.TotalTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Initial Waiting Time:</td>
+                                <td>{{task.InitialWaitingTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Waiting Time:</td>
+                                <td>{{task.WaitingTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Calculating Time:</td>
+                                <td>{{task.CalculatingTime}}</td>
+                            </tr>
+                            <tr>
+                                <td>Transfer Time:</td>
+                                <td>{{task.TransferTime}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div class="col-md-6">
+                        <table class="table table-condensed table-no-border table-auto-width table-content">
+                            <tr>
+                                <td>Calculation Runs:</td>
+                                <td>{{task.NumCalculationRuns}}</td>
+                            </tr>
+                            <tr>
+                                <td>Retries Runs:</td>
+                                <td>{{task.NumRetries}}</td>
+                            </tr>
+                            <tr>
+                                <td>Cores Required:</td>
+                                <td>{{task.CoresRequired}}</td>
+                            </tr>
+                            <tr>
+                                <td>Memory Required:</td>
+                                <td>{{task.MemoryRequired}}</td>
+                            </tr>
+                            <tr>
+                                <td>Priority:</td>
+                                <td>{{task.Priority}}</td>
+                            </tr>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </tab>
+        <tab heading="Exception" ng-show="task.Exception">
+            <div style="overflow:auto;">
+                <p style="word-wrap: break-word;">{{task.Exception}}</p>
+            </div>
+        </tab>
+    </tabset>
+</div>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialogCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialogCtrl.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/details/jobTaskDetailsDialogCtrl.js	(revision 12516)
@@ -0,0 +1,14 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.jobTaskDetailsDialogCtrl',
+        ['$scope', '$modalInstance', 'taskNo', 'task', function ($scope, $modalInstance, taskNo, task) {
+            $scope.taskNo = taskNo;
+            $scope.task = task;
+
+            $scope.close = function () {
+                $modalInstance.dismiss('cancel');
+            };
+
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobs.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobs.cshtml	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobs.cshtml	(revision 12516)
@@ -10,29 +10,28 @@
                 <a ng-href="#/statistics/users">Users</a>
             </li>
-            <li>
-                <a ng-href="#/statistics/clients">Clients</a>
-            </li>
         }
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
     </ul>
 </header>
 
 <div class="default-view-container">
-
-
     <div class="row">
         <div class="col-lg-12">
             <div class="panel panel-default">
                 <div class="panel-heading">
-                    <h3 class="panel-title">My Jobs</h3>
+                    <h3 class="panel-title">Current Jobs</h3>
                 </div>
                 <div class="panel-body">
                     <table class="table table-hover table-condensed">
                         <thead>
-                            <tr>
-                                <th>#</th>
-                                <th>Job Name</th>
-                                <th>Date Created</th>
-                                <th></th>
-                            </tr>
+                        <tr>
+                            <th>#</th>
+                            <th>Job Name</th>
+                            <th>Date Created</th>
+                            <th>Progress</th>
+                            <th></th>
+                        </tr>
                         </thead>
                         <tr ng-repeat="job in jobs">
@@ -40,8 +39,13 @@
                             <td>{{job.Name}}</td>
                             <td>{{job.DateCreated}}</td>
-                            <td><a ng-href="" data-ng-click="getTasksByJobId(job.Id)">Load Tasks</a></td>
+                            <td>
+                                <progressbar class="progress active" max="job.TotalTasks" value="job.CompletedTasks" type="success"><i style="color:black; white-space:nowrap;">{{job.CompletedTasks}} / {{job.TotalTasks}}</i></progressbar>
+                            </td>
+                            <td>
+                                <a ng-href="#/statistics/jobs/{{job.Id}}">Details</a>
+                            </td>
                         </tr>
                         <tr ng-hide="jobs.length">
-                            <td colspan="4" class="text-center">No jobs found!</td>
+                            <td colspan="5" class="text-center">No active jobs found!</td>
                         </tr>
                     </table>
@@ -51,61 +55,41 @@
     </div>
 
-    <div class="row" ng-show="tasks.length">
-
-        <div class="col-lg-3 col-md-6" ng-repeat="task in tasks">
+    <div class="row">
+        <div class="col-lg-12">
             <div class="panel panel-default">
-
                 <div class="panel-heading">
-                    <h3 class="panel-title">Task {{$index + 1}}</h3>
+                    <h3 class="panel-title">Completed Jobs</h3>
                 </div>
                 <div class="panel-body">
-                    <table class="table table-hover table-condensed table-no-border table-auto-width">
+                    <table class="table table-hover table-condensed">
+                        <thead>
                         <tr>
-                            <td>Initial Waiting Time:</td>
-                            <td>{{task.InitialWaitingTime}}</td>
+                            <th>#</th>
+                            <th>Job Name</th>
+                            <th>Date Created</th>
+                            <th>Date Completed</th>
+                            <th>Tasks</th>
+                            <th></th>
                         </tr>
-                        <tr>
-                            <td>Waiting Time:</td>
-                            <td>{{task.WaitingTime}}</td>
+                        </thead>
+                        <tr ng-repeat="job in completedJobPage.Jobs">
+                            <td>{{($index + 1)+((completedJobCurPage-1)*(completedJobPageSize))}}</td>
+                            <td>{{job.Name}}</td>
+                            <td>{{job.DateCreated}}</td>
+                            <td>{{job.DateCompleted}}</td>
+                            <td>{{job.TotalTasks}}</td>
+                            <td><a ng-href="#/statistics/jobs/{{job.Id}}">Details</a></td>
                         </tr>
-                        <tr>
-                            <td>Calculating Time:</td>
-                            <td>{{task.CalculatingTime}}</td>
-                        </tr>
-                        <tr>
-                            <td>Transfer Time:</td>
-                            <td>{{task.TransferTime}}</td>
-                        </tr>
-                        <tr>
-                            <td>Start Time:</td>
-                            <td>{{task.StartTime}}</td>
-                        </tr>
-                        <tr>
-                            <td>End Time:</td>
-                            <td>{{task.EndTime}}</td>
-                        </tr>
-                        <tr>
-                            <td>Calculation Runs:</td>
-                            <td>{{task.NumCalculationRuns}}</td>
-                        </tr>
-                        <tr>
-                            <td>Retries Runs:</td>
-                            <td>{{task.NumRetries}}</td>
-                        </tr>
-                        <tr>
-                            <td>Cores Required:</td>
-                            <td>{{task.CoresRequired}}</td>
-                        </tr>
-                        <tr>
-                            <td>Memory Required:</td>
-                            <td>{{task.MemoryRequired}}</td>
-                        </tr>
-                        <tr>
-                            <td>Priority:</td>
-                            <td>{{task.Priority}}</td>
+                        <tr ng-hide="completedJobPage.Jobs.length">
+                            <td colspan="6" class="text-center">No completed jobs found!</td>
                         </tr>
                     </table>
+                    <div class="row text-center" ng-show="completedJobPage.TotalJobs > completedJobPage.Jobs.length">
+                        <pagination max-size="10" total-items="completedJobPage.TotalJobs"
+                                    ng-model="completedJobCurPage" items-per-page="completedJobPageSize"
+                                    ng-change="changeCompletedJobPage()"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
                 </div>
-
             </div>
         </div>
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobsCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobsCtrl.js	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/jobs/jobsCtrl.js	(revision 12516)
@@ -2,18 +2,47 @@
     var module = appStatisticsPlugin.getAngularModule();
     module.controller('app.statistics.jobsCtrl',
-        ['$scope', 'app.statistics.data.service', function($scope, statisticsService) {
-            var getJobs = function() {
-                statisticsService.getJobs({}, function(jobs) {
+        ['$scope', '$interval', 'app.statistics.jobService', function ($scope, $interval, jobService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.completedJobCurPage = 1;
+            $scope.completedJobPageSize = 20;
+
+            var getAllJobs = function() {
+                jobService.getAllJobs({ completed: false }, function(jobs) {
                     $scope.jobs = jobs;
+                    var length = $scope.jobs.length;
+                    for (var i = 0; i < length; ++i) {
+                        $scope.jobs[i].DateCreated = CSharpDateToString($scope.jobs[i].DateCreated);
+                    }
                 });
             };
 
-            $scope.getTasksByJobId = function(id) {
-                statisticsService.getTasksByJobId({ id: id }, function(tasks) {
-                    $scope.tasks = tasks;
-                });
+            var getCompletedJobs = function() {
+                jobService.getJobs({ page: $scope.completedJobCurPage, size: $scope.completedJobPageSize, completed: true },
+                    function (jobPage) {
+                        $scope.completedJobPage = jobPage;
+                        var length = $scope.completedJobPage.Jobs.length;
+                        for (var i = 0; i < length; ++i) {
+                            $scope.completedJobPage.Jobs[i].DateCreated = CSharpDateToString($scope.completedJobPage.Jobs[i].DateCreated);
+                            $scope.completedJobPage.Jobs[i].DateCompleted = CSharpDateToString($scope.completedJobPage.Jobs[i].DateCompleted);
+                        }
+                    }
+                );
             };
 
-            getJobs();
+            $scope.changeCompletedJobPage = function () {
+                update();
+            };
+
+            var update = function () {
+                getAllJobs();
+                getCompletedJobs();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
         }]
     );
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/clientService.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/clientService.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/clientService.js	(revision 12516)
@@ -0,0 +1,13 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Client/';
+    module.factory('app.statistics.clientService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states', expired: '@expired', start: '@start', end: '@end' }, {
+                getClientDetails: { method: 'GET', params: { action: 'GetClientDetails' } },
+                getClients: { method: 'GET', params: { action: 'GetClients' } },
+                getClientHistory: { method: 'GET', params: { action: 'GetClientHistory' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/jobService.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/jobService.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/jobService.js	(revision 12516)
@@ -0,0 +1,16 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Job/';
+    module.factory('app.statistics.jobService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states', completed: '@completed' }, {
+                getJobDetails: { method: 'GET', params: { action: 'GetJobDetails' } },
+                getJobs: { method: 'GET', params: { action: 'GetJobs' } },
+                getAllJobs: { method: 'GET', params: { action: 'GetAllJobs'}, isArray: true },
+                getJobsByUserId: { method: 'GET', params: { action: 'GetJobsByUserId' } },
+                getAllJobsByUserId: { method: 'GET', params: { action: 'GetAllJobsByUserId'}, isArray: true },
+                getJobTasksStatesByJobId: { method: 'GET', params: { action: 'GetJobTasksStatesByJobId' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/statisticsService.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/statisticsService.js	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/statisticsService.js	(revision 12516)
@@ -4,8 +4,24 @@
     module.factory('app.statistics.data.service',
         ['$resource', function ($resource) {
-            return $resource(apiUrl + ':action', { id: '@id' }, {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states', expired: '@expired'}, {
+                getJob: { method: 'GET', params: { action: 'GetJob' } },
                 getJobs: { method: 'GET', params: { action: 'GetJobs' }, isArray: true },
+                getCompletedJobs: { method: 'GET', params: { action: 'GetCompletedJobs' }, isArray: true },
+                getCompletedJobsByUserId: { method: 'GET', params: { action: 'GetCompletedJobsByUserId' }, isArray: true },
+                getCompletedJobsCount: { method: 'GET', params: { action: 'GetCompletedJobsCount' } },
+                getCompletedJobsCountByUserId: { method: 'GET', params: { action: 'GetCompletedJobsCountByUserId' } },
                 getJobsByUserId: { method: 'GET', params: { action: 'GetJobsByUserId' }, isArray: true },
-                getTasksByJobId: { method: 'GET', params: { action: 'GetTasksByJobId' }, isArray: true }
+                getTaskCountByJobId: { method: 'GET', params: { action: 'GetTaskCountByJobId' } },
+                getTasksByJobId: { method: 'GET', params: { action: 'GetTasksByJobId' }, isArray: true },
+                getJobTasksStatesByJobId: { method: 'GET', params: { action: 'GetJobTasksStatesByJobId' }, isArray: true },
+                getClient: { method: 'GET', params: { action: 'GetClient' } },
+                getClientCount: { method: 'GET', params: { action: 'GetClientCount' } },
+                getClients: { method: 'GET', params: { action: 'GetClients' }, isArray: true },
+                getTaskCountByClientId: { method: 'GET', params: { action: 'GetTaskCountByClientId' } },
+                getTasksByClientId: { method: 'GET', params: { action: 'GetTasksByClientId' }, isArray: true },
+                getJobTasksStatesByClientId: { method: 'GET', params: { action: 'GetJobTasksStatesByClientId' }, isArray: true },
+                getClientHistory: { method: 'GET', params: { action: 'GetClientHistory' }, isArray: true },
+                getUsers: { method: 'GET', params: { action: 'GetUsers' }, isArray: true },
+                getUser: { method: 'GET', params: { action: 'GetUser' } }
             });
         }]
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/taskService.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/taskService.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/taskService.js	(revision 12516)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/Task/';
+    module.factory('app.statistics.taskService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id', page: '@page', size: '@size', states: '@states' }, {
+                getTasksByJobId: { method: 'POST', params: { action: 'GetTasksByJobId' } },
+                getTasksByClientId: { method: 'POST', params: { action: 'GetTasksByClientId' } }
+            });
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/userService.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/userService.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/services/userService.js	(revision 12516)
@@ -0,0 +1,12 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    var apiUrl = 'api/Statistics/User/';
+    module.factory('app.statistics.userService',
+        ['$resource', function ($resource) {
+            return $resource(apiUrl + ':action', { id: '@id' }, {
+                getUser: { method: 'GET', params: { action: 'GetUser' } },
+                getUsers: { method: 'GET', params: { action: 'GetUsers' }, isArray: true }
+            });
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/statistics.css
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/statistics.css	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/statistics.css	(revision 12516)
@@ -1,1 +1,6 @@
-﻿
+﻿#job-filter-header {
+  padding: 30px;
+  background: #F5F5F5;
+  border-top: 1px solid #D3D3D3;
+  border-bottom: 1px solid #D3D3D3;
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetails.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetails.cshtml	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetails.cshtml	(revision 12516)
@@ -0,0 +1,136 @@
+﻿@using HeuristicLab.Services.Access.DataTransfer
+@using HeuristicLab.Services.Hive
+@if (Request.IsAuthenticated && User.IsInRole(HiveRoles.Administrator)) {
+<header class="view-header">
+    <ul class="nav nav-list nav-list-topbar pull-left">
+        <li>
+            <a ng-href="#/statistics/jobs">Jobs</a>
+        </li>
+        <li class="active">
+            <a ng-href="#/statistics/users">Users</a>
+        </li>
+        <li>
+            <a ng-href="#/statistics/clients">Clients</a>
+        </li>
+    </ul>
+</header>
+
+<div class="default-view-container">
+    <div class="row">
+        <div class="col-lg-9 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">User Details</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Name:</td>
+                            <td>{{user.Name}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-lg-3 col-md-6">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Tasks Overview</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-no-border table-condensed table-auto-width table-content">
+                        <tr>
+                            <td class="text-left">Total:</td>
+                            <td class="text-right">{{totalJobTasks}}</td>
+                        </tr>
+                        <tr ng-repeat="state in states">
+                            <td class="text-left">{{state.State}}:</td>
+                            <td class="text-right">{{state.Count}}</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Current Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Job Name</th>
+                                <th>Date Created</th>
+                                <th>Progress</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="job in jobs">
+                            <td>{{$index + 1}}</td>
+                            <td>{{job.Name}}</td>
+                            <td>{{job.DateCreated}}</td>
+                            <td>
+                                <progressbar class="progress active" max="job.TotalTasks" value="job.CompletedTasks" type="success"><i style="color:black; white-space:nowrap;">{{job.CompletedTasks}} / {{job.TotalTasks}}</i></progressbar>
+                            </td>
+                            <td>
+                                <a ng-href="#/statistics/jobs/{{job.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="jobs.length">
+                            <td colspan="5" class="text-center">No active jobs found!</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Completed Jobs</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Job Name</th>
+                                <th>Date Created</th>
+                                <th>Date Completed</th>
+                                <th>Tasks</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="job in completedJobPage.Jobs">
+                            <td>{{($index + 1)+((completedJobCurPage-1)*(completedJobPageSize))}}</td>
+                            <td>{{job.Name}}</td>
+                            <td>{{job.DateCreated}}</td>
+                            <td>{{job.DateCompleted}}</td>
+                            <td>{{job.TotalTasks}}</td>
+                            <td><a ng-href="#/statistics/jobs/{{job.Id}}">Details</a></td>
+                        </tr>
+                        <tr ng-hide="completedJobPage.Jobs.length">
+                            <td colspan="6" class="text-center">No completed jobs found!</td>
+                        </tr>
+                    </table>
+                    <div class="row text-center" ng-show="completedJobPage.TotalJobs > completedJobPage.Jobs.length">
+                        <pagination max-size="10" total-items="completedJobPage.TotalJobs"
+                                    ng-model="completedJobCurPage" items-per-page="completedJobPageSize"
+                                    ng-change="changeCompletedJobPage()"
+                                    boundary-links="true" rotate="false" num-pages="numPages"></pagination>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
+}
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetailsCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetailsCtrl.js	(revision 12516)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/details/userDetailsCtrl.js	(revision 12516)
@@ -0,0 +1,58 @@
+﻿(function () {
+    var module = appStatisticsPlugin.getAngularModule();
+    module.controller('app.statistics.userDetailsCtrl',
+        ['$scope', '$stateParams', '$interval', 'app.statistics.userService', 'app.statistics.jobService',
+        function ($scope, $stateParams, $interval, userService, jobService) {
+            $scope.interval = defaultPageUpdateInterval;
+            $scope.completedJobCurPage = 1;
+            $scope.completedJobPageSize = 20;
+
+
+            var getUserDetails = function () {
+                userService.getUser({ id: $stateParams.id }, function(user) {
+                    $scope.user = user;
+                });
+            };
+
+            var getAllJobs = function () {
+                jobService.getAllJobsByUserId({ id: $stateParams.id, completed: false }, function (jobs) {
+                    $scope.jobs = jobs;
+                    var length = $scope.jobs.length;
+                    for (var i = 0; i < length; ++i) {
+                        $scope.jobs[i].DateCreated = CSharpDateToString($scope.jobs[i].DateCreated);
+                    }
+                });
+            };
+
+            var getCompletedJobs = function () {
+                jobService.getJobsByUserId({ id: $stateParams.id, page: $scope.completedJobCurPage, size: $scope.completedJobPageSize, completed: true },
+                    function (jobPage) {
+                        $scope.completedJobPage = jobPage;
+                        var length = $scope.completedJobPage.Jobs.length;
+                        for (var i = 0; i < length; ++i) {
+                            $scope.completedJobPage.Jobs[i].DateCreated = CSharpDateToString($scope.completedJobPage.Jobs[i].DateCreated);
+                            $scope.completedJobPage.Jobs[i].DateCompleted = CSharpDateToString($scope.completedJobPage.Jobs[i].DateCompleted);
+                        }
+                    }
+                );
+            };
+
+            $scope.changeCompletedJobPage = function () {
+                update();
+            };
+
+            var update = function () {
+                getUserDetails();
+                getAllJobs();
+                getCompletedJobs();
+            };
+
+            $scope.updateInterval = $interval(update, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            update(); // init page
+        }]
+    );
+})();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/users.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/users.cshtml	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/users.cshtml	(revision 12516)
@@ -17,5 +17,34 @@
 
 <div class="default-view-container">
-
+    <div class="row">
+        <div class="col-lg-12">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <h3 class="panel-title">Users</h3>
+                </div>
+                <div class="panel-body">
+                    <table class="table table-hover table-condensed">
+                        <thead>
+                            <tr>
+                                <th>#</th>
+                                <th>Username</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+                        <tr ng-repeat="user in users">
+                            <td>{{$index + 1}}</td>
+                            <td>{{user.Name}}</td>
+                            <td>
+                                <a ng-href="#/statistics/users/{{user.Id}}">Details</a>
+                            </td>
+                        </tr>
+                        <tr ng-hide="users.length">
+                            <td colspan="4" class="text-center">No users found!</td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
 </div>
 }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/usersCtrl.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/usersCtrl.js	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/WebApp/users/usersCtrl.js	(revision 12516)
@@ -2,6 +2,20 @@
     var module = appStatisticsPlugin.getAngularModule();
     module.controller('app.statistics.usersCtrl',
-        ['$scope', 'app.statistics.data.service', function($scope, statisticsService) {
-            
+        ['$scope', '$interval', 'app.statistics.userService', function ($scope, $interval, userService) {
+            $scope.interval = defaultPageUpdateInterval;
+
+            var getUsers = function() {
+                userService.getUsers({}, function(users) {
+                    users.splice(0, 1);
+                    $scope.users = users;
+                });
+            };
+
+            $scope.updateInterval = $interval(getUsers, $scope.interval);
+            var cancelInterval = $scope.$on('$locationChangeSuccess', function () {
+                $interval.cancel($scope.updateInterval);
+                cancelInterval();
+            });
+            getUsers(); // init page
         }]
     );
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/statistics.js
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/statistics.js	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Statistics/3.3/statistics.js	(revision 12516)
@@ -2,10 +2,20 @@
 (function () {
     var plugin = appStatisticsPlugin;
-    plugin.dependencies = ['ngResource', 'ui.knob', 'ui.bootstrap'];
+    plugin.dependencies = ['ngResource', 'ui.knob', 'ui.bootstrap', 'ngFitText'];
     plugin.files = [
+        'WebApp/statistics.css',
         'WebApp/services/statisticsService.js',
+        'WebApp/services/jobService.js',
+        'WebApp/services/taskService.js',
+        'WebApp/services/userService.js',
+        'WebApp/services/clientService.js',
         'WebApp/jobs/jobsCtrl.js',
+        'WebApp/jobs/details/jobDetailsCtrl.js',
+        'WebApp/jobs/details/jobTaskDetailsDialogCtrl.js',
         'WebApp/clients/clientsCtrl.js',
+        'WebApp/clients/details/clientDetailsCtrl.js',
+        'WebApp/clients/details/clientTaskDetailsDialogCtrl.js',
         'WebApp/users/usersCtrl.js',
+        'WebApp/users/details/userDetailsCtrl.js'
     ];
     plugin.view = 'WebApp/jobs/jobs.cshtml';
@@ -13,6 +23,9 @@
     plugin.routes = [
         new Route('jobs', 'WebApp/jobs/jobs.cshtml', 'app.statistics.jobsCtrl'),
+        new Route('jobs/:id', 'WebApp/jobs/details/jobDetails.cshtml', 'app.statistics.jobDetailsCtrl'),
         new Route('clients', 'WebApp/clients/clients.cshtml', 'app.statistics.clientsCtrl'),
-        new Route('users', 'WebApp/users/users.cshtml', 'app.statistics.usersCtrl')
+        new Route('clients/:id', 'WebApp/clients/details/clientDetails.cshtml', 'app.statistics.clientDetailsCtrl'),
+        new Route('users', 'WebApp/users/users.cshtml', 'app.statistics.usersCtrl'),
+        new Route('users/:id', 'WebApp/users/details/userDetails.cshtml', 'app.statistics.userDetailsCtrl')
     ];
     var menu = app.getMenu();
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataTransfer/Status.cs
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataTransfer/Status.cs	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApi/DataTransfer/Status.cs	(revision 12516)
@@ -20,4 +20,5 @@
 #endregion
 
+using System;
 using System.Collections.Generic;
 
@@ -61,8 +62,18 @@
   }
 
+  public class TimeStatus {
+    public long MinCalculatingTime { get; set; }
+    public long MaxCalculatingTime { get; set; }
+    public long AvgCalculatingTime { get; set; }
+    public long AvgWaitingTime { get; set; }
+    public long TotalCpuTime { get; set; }
+    public DateTime BeginDate { get; set; }
+  }
+
   public class Status {
     public CoreStatus CoreStatus { get; set; }
     public CpuUtilizationStatus CpuUtilizationStatus { get; set; }
     public MemoryStatus MemoryStatus { get; set; }
+    public TimeStatus TimeStatus { get; set; }
     public IEnumerable<TaskStatus> TasksStatus { get; set; }
     public IEnumerable<SlaveStatus> SlavesStatus { get; set; }
Index: /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/history.cshtml
===================================================================
--- /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/history.cshtml	(revision 12515)
+++ /branches/HiveStatistics/sources/HeuristicLab.Services.WebApp.Status/3.3/WebApp/history/history.cshtml	(revision 12516)
@@ -56,7 +56,4 @@
                 <div class="panel-body">
                     <flot dataset="coreSeries" options="fillChartOptions"></flot>
-                    <p>{{maxUpper}}</p>
-                    <p>{{fromDateTime}}</p>
-                    <p>{{toDateTime}}</p>
                 </div>
             </div>
