Merge commit 'a9d8e4c015d964b15ddf766327dfa3b0aae55a02'

This commit is contained in:
Chris.Watts90@outlook.com 2018-06-06 12:13:25 +01:00
commit 3699aff489
34 changed files with 521 additions and 159 deletions

View File

@ -12,10 +12,10 @@ namespace CardReaderService
{ {
private Thread _mainWorkThread; private Thread _mainWorkThread;
private bool _stopMainWorkerThread; private bool _stopMainWorkerThread;
private AutoResetEvent _mainWorkerTerminationSignal;
private string _readerName = ""; private string _readerName = "";
//private SCardReader _reader;
private SCardMonitor _cardMonitor; private SCardMonitor _cardMonitor;
private bool _initialised=false;
public Service1() public Service1()
{ {
@ -24,29 +24,30 @@ namespace CardReaderService
public void Start() public void Start()
{ {
OnStart(new string[] {}); OnStart(new string[] { });
} }
//
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
Console.WriteLine("Starting.. Getting available readers"); StartWorkerThread();
var ctxFactory = ContextFactory.Instance; }
using(var context = ctxFactory.Establish(SCardScope.System))
{
var readerNames = context.GetReaders();
if (NoReaderAvailable(readerNames))
{
Console.WriteLine("No Card Reader is available, Exiting..");
return;
}
Console.WriteLine("Choosing first available reader: " + readerNames.First());
_readerName = readerNames.First();
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += _cardMonitor_CardInserted;
_cardMonitor.Start(_readerName);
StartWorkerThread(); private bool WeHaveValidCardReader()
{
if (_cardMonitor == null)
{
return false;
} }
Console.WriteLine(_cardMonitor.GetCurrentState(0));
if (_cardMonitor.GetCurrentState(0) == SCRState.Unknown
|| _cardMonitor.GetCurrentState(0) == SCRState.Unavailable
|| _cardMonitor.GetCurrentState(0) == (SCRState.Ignore | SCRState.Unavailable)
//|| _cardMonitor.GetCurrentState(0) == SCRState.Unaware //if we say this is an invalid state, we cause a memory leak where we create a duplicate card monitor, subscribe and overwrite.
)
{
return false;
}
return true;
} }
public void Stop() public void Stop()
@ -57,19 +58,16 @@ namespace CardReaderService
protected override void OnStop() protected override void OnStop()
{ {
_stopMainWorkerThread = true; _stopMainWorkerThread = true;
if (_mainWorkThread!= null && _mainWorkThread.IsAlive) _mainWorkerTerminationSignal.Set();
if (_mainWorkThread != null && _mainWorkThread.IsAlive)
{ {
_mainWorkThread.Join(3000); _mainWorkThread.Interrupt();
if (_mainWorkThread.IsAlive)
{
_mainWorkThread.Interrupt();
}
} }
if (_cardMonitor != null) if (_cardMonitor != null)
{ {
_cardMonitor.Cancel(); _cardMonitor.Cancel();
_cardMonitor.Dispose(); _cardMonitor.Dispose();
_cardMonitor = null; _cardMonitor = null;
} }
} }
@ -81,6 +79,7 @@ namespace CardReaderService
Name = "CardServiceMainThread", Name = "CardServiceMainThread",
IsBackground = false IsBackground = false
}; };
_mainWorkerTerminationSignal = new AutoResetEvent(false);
_mainWorkThread.Start(); _mainWorkThread.Start();
} }
@ -99,7 +98,7 @@ namespace CardReaderService
_stopMainWorkerThread = true; _stopMainWorkerThread = true;
return; return;
} }
var err = reader.Connect(_readerName, SCardShareMode.Shared, SCardProtocol.T0 | SCardProtocol.T1); var err = reader.Connect(_readerName, SCardShareMode.Shared, SCardProtocol.T0 | SCardProtocol.T1);
if (err == SCardError.Success) if (err == SCardError.Success)
@ -113,10 +112,10 @@ namespace CardReaderService
var atrString = ConvertByteUIDToString(e.Atr); var atrString = ConvertByteUIDToString(e.Atr);
Console.WriteLine("Card Inserted, ATR: " + atrString + ", and UID is: " + uid); Console.WriteLine("Card Inserted, ATR: " + atrString + ", and UID is: " + uid);
CardDataPost postObj = new CardDataPost {CardUId = uid}; CardDataPost postObj = new CardDataPost { CardUId = uid };
DataCenterHelper.PostAsync(postObj, "/api/swipedata"); DataCenterHelper.PostAsync(postObj, "/api/swipedata");
Console.WriteLine("Posted to Server"); Console.WriteLine("Posted to Server");
} }
} }
else else
{ {
@ -125,21 +124,53 @@ namespace CardReaderService
} }
} }
private void MainWorkerThread() private void MainWorkerThread()
{ {
while (!_stopMainWorkerThread) while (!_stopMainWorkerThread)
{ {
//dont actually need to do anything.. but cannot exit right away? if (!WeHaveValidCardReader())
Thread.Sleep(3000); { //only do this if we don't have a valid card reader
if (_initialised)
{
//card reader no longer available, tidy up.
_cardMonitor.Cancel();
_cardMonitor.CardInserted -= _cardMonitor_CardInserted;
_cardMonitor.Dispose();
_cardMonitor = null;
_initialised = false;
}
Console.WriteLine("Starting.. Getting available readers");
var ctxFactory = ContextFactory.Instance;
using (var context = ctxFactory.Establish(SCardScope.System))
{
var readerNames = context.GetReaders();
if (!NoReaderAvailable(readerNames))
{
//we have a reader available, so initialise!
Console.WriteLine("Choosing first available reader: " + readerNames.First());
_readerName = readerNames.First();
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += _cardMonitor_CardInserted;
_cardMonitor.Start(_readerName);
_initialised = true;
}
else
{
Console.WriteLine("No Card Reader is available..");
}
}
}
_mainWorkerTerminationSignal.WaitOne(3000);
} }
} }
private string ConvertByteUIDToString(byte[] uid) private string ConvertByteUIDToString(byte[] uid)
{ {
StringBuilder sb = new StringBuilder(); var sb = new StringBuilder();
foreach(var b in uid) foreach (var b in uid)
{ {
sb.AppendFormat("{0:X2}", b); sb.AppendFormat("{0:X2}", b);
} }

View File

@ -18,6 +18,9 @@ namespace CardReaderService
private string _readerName = ""; private string _readerName = "";
private SCardMonitor _cardMonitor; private SCardMonitor _cardMonitor;
private bool _initialised = false;
private AutoResetEvent _mainWorkerTerminationSignal;
private ILogger _logger; private ILogger _logger;
public CardReaderService() public CardReaderService()
@ -32,45 +35,9 @@ namespace CardReaderService
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
var configPath = string.Concat(System.Reflection.Assembly.GetEntryAssembly().Location, ".config");
_logger = NinjectHelper.GetInstance().Get<ILogger>(); _logger = NinjectHelper.GetInstance().Get<ILogger>();
_logger.Trace("Starting Service.. Getting available readers");
var ctxFactory = ContextFactory.Instance; StartWorkerThread();
using(var context = ctxFactory.Establish(SCardScope.System))
{
var readerNames = context.GetReaders();
if (NoReaderAvailable(readerNames))
{
_logger.Trace("No Card Reader is available, Exiting..");
throw new ApplicationException("A card reader must be provided in order to operate.");
}
foreach (var reader in readerNames)
{
_logger.Trace("Found reader: {0}", reader);
}
var readerNameConfig = ConfigurationHandler.ConfigurationHandler.GetConfiguration("ReaderName");
if (string.IsNullOrEmpty(readerNameConfig))
{
if (!readerNames.Contains(readerNameConfig))
{
_logger.Warn("No reader found with the name: {0}, defaulting to first available reader {1}",
readerNameConfig, readerNames.First());
readerNameConfig=readerNames.First();
}
}
_logger.Trace("Choosing reader: {0}", readerNameConfig);
_readerName = readerNameConfig;
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += _cardMonitor_CardInserted;
_cardMonitor.Start(_readerName);
StartWorkerThread();
}
} }
public void StopService() public void StopService()
@ -81,6 +48,7 @@ namespace CardReaderService
protected override void OnStop() protected override void OnStop()
{ {
_stopMainWorkerThread = true; _stopMainWorkerThread = true;
_mainWorkerTerminationSignal.Set();
if (_mainWorkThread!= null && _mainWorkThread.IsAlive) if (_mainWorkThread!= null && _mainWorkThread.IsAlive)
{ {
_mainWorkThread.Join(3000); _mainWorkThread.Join(3000);
@ -104,6 +72,7 @@ namespace CardReaderService
Name = "CardServiceMainThread", Name = "CardServiceMainThread",
IsBackground = false IsBackground = false
}; };
_mainWorkerTerminationSignal = new AutoResetEvent(false);
_mainWorkThread.Start(); _mainWorkThread.Start();
} }
@ -137,7 +106,7 @@ namespace CardReaderService
_logger.Trace("Card Inserted, ATR: " + atrString + ", and UID is: " + uid); _logger.Trace("Card Inserted, ATR: " + atrString + ", and UID is: " + uid);
var postObj = new CardDataPost {CardUId = uid}; var postObj = new CardDataPost {CardUId = uid};
DataCenterHelper.PostAsync(postObj, "/api/swipedata"); DataCenterHelper.PostAsync(_logger, postObj, "/api/swipedata");
_logger.Trace("Posted to Server"); _logger.Trace("Posted to Server");
} }
else else
@ -153,7 +122,51 @@ namespace CardReaderService
while (!_stopMainWorkerThread) while (!_stopMainWorkerThread)
{ {
//dont actually need to do anything.. but cannot exit right away? //dont actually need to do anything.. but cannot exit right away?
Thread.Sleep(3000); if (!WeHaveValidCardReader())
{
if (_initialised)
{
//card reader no longer available, tidy up.
_cardMonitor.Cancel();
_cardMonitor.CardInserted -= _cardMonitor_CardInserted;
_cardMonitor.Dispose();
_cardMonitor = null;
_initialised = false;
}
var ctxFactory = ContextFactory.Instance;
using (var context = ctxFactory.Establish(SCardScope.System))
{
var readerNames = context.GetReaders();
if (NoReaderAvailable(readerNames))
{
_logger.Trace("No Card Reader is available..");
}
else
{
foreach (var reader in readerNames)
{
_logger.Trace("Found reader: {0}", reader);
}
var readerNameConfig = ConfigurationHandler.ConfigurationHandler.GetConfiguration("ReaderName");
if (string.IsNullOrEmpty(readerNameConfig) || (!readerNames.Contains(readerNameConfig)))
{
_logger.Warn("No reader found with the name: {0}, defaulting to first available reader {1}",
readerNameConfig, readerNames.First());
readerNameConfig = readerNames.First();
}
_logger.Trace("Choosing reader: {0}", readerNameConfig);
_readerName = readerNameConfig;
_cardMonitor = new SCardMonitor(ctxFactory, SCardScope.System);
_cardMonitor.CardInserted += _cardMonitor_CardInserted;
_cardMonitor.Start(_readerName);
_initialised = true;
}
}
}
_mainWorkerTerminationSignal.WaitOne(3000);
} }
} }
@ -171,5 +184,23 @@ namespace CardReaderService
{ {
return readerNames == null || readerNames.Count < 1; return readerNames == null || readerNames.Count < 1;
} }
private bool WeHaveValidCardReader()
{
if (_cardMonitor == null)
{
return false;
}
_logger.Trace(_cardMonitor.GetCurrentState(0).ToString());
if (_cardMonitor.GetCurrentState(0) == SCRState.Unknown
|| _cardMonitor.GetCurrentState(0) == SCRState.Unavailable
|| _cardMonitor.GetCurrentState(0) == (SCRState.Ignore | SCRState.Unavailable)
//|| _cardMonitor.GetCurrentState(0) == SCRState.Unaware //if we say this is an invalid state, we cause a memory leak where we create a duplicate card monitor, subscribe and overwrite.
)
{
return false;
}
return true;
}
} }
} }

View File

@ -5,13 +5,14 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Interfaces;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace CardReaderService namespace CardReaderService
{ {
static class DataCenterHelper static class DataCenterHelper
{ {
public static void Post(CardDataPost postObject, string url) public static void Post(ILogger logger, CardDataPost postObject, string url)
{ {
var endpointConfig = ConfigurationHandler.ConfigurationHandler.GetConfiguration("DataCenterServiceEndpoint") ?? var endpointConfig = ConfigurationHandler.ConfigurationHandler.GetConfiguration("DataCenterServiceEndpoint") ??
"http://localhost:8800"; "http://localhost:8800";
@ -22,10 +23,10 @@ namespace CardReaderService
var content = new StringContent(jsonObject, Encoding.UTF8, "application/json"); var content = new StringContent(jsonObject, Encoding.UTF8, "application/json");
try try
{ {
Console.WriteLine("Writing"); logger.Trace("Writing");
var fullUrl = endpointConfig + url; var fullUrl = endpointConfig + url;
var response = client.PostAsync(fullUrl, content).Result; var response = client.PostAsync(fullUrl, content).Result;
Console.WriteLine("Written"); logger.Trace("Written");
} }
catch (Exception) catch (Exception)
{ {
@ -35,9 +36,9 @@ namespace CardReaderService
} }
} }
public static Task PostAsync(CardDataPost postObject, string url) public static Task PostAsync(ILogger logger, CardDataPost postObject, string url)
{ {
return Task.Run(() => Post(postObject, url)); return Task.Run(() => Post(logger, postObject, url));
} }
} }

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.5.0")] [assembly: AssemblyVersion("0.2.1.0")]
[assembly: AssemblyFileVersion("0.1.5.0")] [assembly: AssemblyFileVersion("0.2.1.0")]

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("0.2.1.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("0.2.1.0")]

View File

@ -37,6 +37,14 @@
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups> <RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir> <RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\ConfigurationHandler\ConfigurationHandler.csproj">
<Name>ConfigurationHandler</Name>
<Project>{115250f6-f8c4-4f9b-a15f-251ea258d963}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
<ProjectReference Include="..\Interfaces\Interfaces.csproj"> <ProjectReference Include="..\Interfaces\Interfaces.csproj">
<Name>Interfaces</Name> <Name>Interfaces</Name>
<Project>{b7347b72-e208-423a-9d99-723b558ea3d7}</Project> <Project>{b7347b72-e208-423a-9d99-723b558ea3d7}</Project>

View File

@ -8,6 +8,12 @@
KeyPath="yes" KeyPath="yes"
Source="$(var.Interfaces.TargetDir)Interfaces.dll" /> Source="$(var.Interfaces.TargetDir)Interfaces.dll" />
</Component> </Component>
<Component Id="ConfigurationHandler" Guid="{54C7ACE7-02DE-4EDC-BDDC-7DC6F34F6AF8}">
<File Id="ConfigurationHandlerDll"
Checksum="yes"
KeyPath="yes"
Source="$(var.ConfigurationHandler.TargetDir)ConfigurationHandler.dll"/>
</Component>
</ComponentGroup> </ComponentGroup>
</Fragment> </Fragment>
</Wix> </Wix>

View File

@ -3,7 +3,7 @@
<Product Id="{DA4797C8-E60E-499F-95A3-B7BD9D19D6DA}" <Product Id="{DA4797C8-E60E-499F-95A3-B7BD9D19D6DA}"
Name="CardReaderServiceInstaller" Name="CardReaderServiceInstaller"
Language="1033" Language="1033"
Version="0.1.5.0" Version="0.2.1.0"
Manufacturer="ChrisWatts" Manufacturer="ChrisWatts"
UpgradeCode="3b61e4ae-d717-4c95-9ce5-a53a1c180bf6"> UpgradeCode="3b61e4ae-d717-4c95-9ce5-a53a1c180bf6">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

View File

@ -1,4 +1,5 @@
using System.Configuration; using System.Configuration;
using System.Linq;
namespace ConfigurationHandler namespace ConfigurationHandler
{ {
@ -7,7 +8,9 @@ namespace ConfigurationHandler
public static string GetConfiguration(string keyName) public static string GetConfiguration(string keyName)
{ {
var appSettings = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetEntryAssembly().Location).AppSettings; var appSettings = ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetEntryAssembly().Location).AppSettings;
return appSettings.Settings[keyName].Value; if(appSettings.Settings.AllKeys.Contains(keyName))
return appSettings.Settings[keyName].Value;
return string.Empty;
} }
} }
} }

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.5.0")] [assembly: AssemblyVersion("0.2.1.0")]
[assembly: AssemblyFileVersion("0.1.5.0")] [assembly: AssemblyFileVersion("0.2.1.0")]

View File

@ -2,7 +2,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"> xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="FlexiTimeSystemInstaller" <Bundle Name="FlexiTimeSystemInstaller"
Version="1.0.0.0" Version="0.2.1.0"
Manufacturer="Chris Watts" Manufacturer="Chris Watts"
UpgradeCode="d38e92db-48f9-40c3-9a6f-d76fbd07326e"> UpgradeCode="d38e92db-48f9-40c3-9a6f-d76fbd07326e">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense"> <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense">

View File

@ -66,6 +66,10 @@ namespace Interfaces
/// <see cref="IdentifierList"/> with nested <see cref="Identifier"/> list /// <see cref="IdentifierList"/> with nested <see cref="Identifier"/> list
/// </returns> /// </returns>
IdentifierList GetUnassignedIdentifierList(); IdentifierList GetUnassignedIdentifierList();
/// <summary>
/// Remove all unassigned identifiers from the system.
/// </summary>
void ClearUnassignedIdentifiers();
/// <summary> /// <summary>
/// Update a user in the system with the new values. /// Update a user in the system with the new values.

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.5.0")] [assembly: AssemblyVersion("0.2.1.0")]
[assembly: AssemblyFileVersion("0.1.5.0")] [assembly: AssemblyFileVersion("0.2.1.0")]

View File

@ -4,8 +4,12 @@
autoReload="true"> autoReload="true">
<targets> <targets>
<target name="viewer" xsi:type="Chainsaw" address="udp://127.0.0.1:4000" /> <target name="viewer" xsi:type="Chainsaw" address="udp://127.0.0.1:4000" />
<target xsi:type="Console" name="consoleViewer"
layout="${longdate}|${level:uppercase=true}|${message}"
detectConsoleAvailable="true" />
</targets> </targets>
<rules> <rules>
<logger name="*" minlevel="Trace" writeTo="viewer" /> <logger name="*" minlevel="Trace" writeTo="viewer" />
<logger name="*" minlevel="Trace" writeTo="consoleViewer" />
</rules> </rules>
</nlog> </nlog>

View File

@ -9,14 +9,14 @@ namespace NLogLogger
{ {
public class NLogger:ILogger public class NLogger:ILogger
{ {
private NLog.Logger _logger; private readonly Logger _logger;
public NLogger() public NLogger()
{ {
var nlogConfigPathOption = var nlogConfigPathOption =
ConfigurationHandler.ConfigurationHandler.GetConfiguration("NLogConfigFilePath"); ConfigurationHandler.ConfigurationHandler.GetConfiguration("NLogConfigFilePath");
if (nlogConfigPathOption == null) if (nlogConfigPathOption == null)
{ {
throw new ArgumentNullException("nlogConfigPath"); throw new ArgumentNullException("nlogConfigPath", "NLogConfigFilePath missing from application config file.");
} }
var nlogConfigPath = new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), nlogConfigPathOption)).LocalPath; var nlogConfigPath = new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), nlogConfigPathOption)).LocalPath;
LogManager.Configuration = new XmlLoggingConfiguration(nlogConfigPath); LogManager.Configuration = new XmlLoggingConfiguration(nlogConfigPath);

View File

@ -4,7 +4,7 @@
<Product Id="{34DA87B7-752D-4A37-928B-650160269E70}" <Product Id="{34DA87B7-752D-4A37-928B-650160269E70}"
Name="SQLiteRepositoryInstaller" Name="SQLiteRepositoryInstaller"
Language="1033" Language="1033"
Version="1.0.0.0" Version="0.2.1.0"
Manufacturer="Chris Watts" UpgradeCode="ec13c45c-6b63-49a6-b058-39e3069a0d6b"> Manufacturer="Chris Watts" UpgradeCode="ec13c45c-6b63-49a6-b058-39e3069a0d6b">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SQLiteRepository
{
class DbVersion
{
public string VersionNumber { get; set; }
}
}

View File

@ -1,6 +1,6 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SQLiteRepository.Properties;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
@ -32,5 +32,14 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.5.0")] [assembly: AssemblyVersion(AssemblyInfo.ASSEMBLY_VERSION)]
[assembly: AssemblyFileVersion("0.1.5.0")] [assembly: AssemblyFileVersion(AssemblyInfo.ASSEMBLY_FILE_VERSION)]
namespace SQLiteRepository.Properties
{
internal static class AssemblyInfo
{
internal const string ASSEMBLY_VERSION = "0.2.1.0";
internal const string ASSEMBLY_FILE_VERSION = "0.2.1.0";
}
}

View File

@ -11,12 +11,37 @@ namespace SQLiteRepository
nameof(TimeLogDb.CalendarWeek) + "=? and " + nameof(TimeLogDb.Year) + "=?)"; nameof(TimeLogDb.CalendarWeek) + "=? and " + nameof(TimeLogDb.Year) + "=?)";
public const string GET_ALL_USERS = public const string GET_ALL_USERS =
"select * from " + nameof(UserIdentity) + " order by " + nameof(UserIdentity.LastName) + " collate nocase, " + "select * from " + nameof(UserIdentity) + " ut "
nameof(UserIdentity.FirstName) + " collate nocase"; + "where "
+ "EXISTS( select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') AND "
+ "NOT EXISTS( select * from " + nameof(UserGroupJoinDb) + " ugp where "
+ nameof(UserGroupJoinDb.UserId_FK) + " = ut.Id"
+ " and " + nameof(UserGroupJoinDb.GroupId_FK) + " = ( "
+ "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )"
+ "order by "
+ nameof(UserIdentity.LastName) + " collate nocase, "
+ nameof(UserIdentity.FirstName) + " collate nocase";
public const string GET_ALL_USERS_PAGINATE = public const string GET_ALL_USERS_PAGINATE =
"select * from " + nameof(UserIdentity) + " order by " + nameof(UserIdentity.LastName) + " collate nocase, " + "select * from " + nameof(UserIdentity) + " ut "
nameof(UserIdentity.FirstName) + " collate nocase limit ? offset ?"; + "where "
+ "EXISTS( select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') AND "
+ "NOT EXISTS( select * from " + nameof(UserGroupJoinDb) + " ugp where "
+ nameof(UserGroupJoinDb.UserId_FK) + " = ut.Id"
+ " and " + nameof(UserGroupJoinDb.GroupId_FK) + " = ( "
+ "select " + nameof(GroupDb.GroupId)
+ " from " + nameof(GroupDb)
+ " where " + nameof(GroupDb.GroupName) + " = 'Archived') )"
+ "order by "
+ nameof(UserIdentity.LastName) + " collate nocase, "
+ nameof(UserIdentity.FirstName) + " collate nocase "
+ "limit ? offset ?";
public const string GET_ALL_USERS_BY_GROUP = public const string GET_ALL_USERS_BY_GROUP =
"select u." + nameof(UserIdentity.Id) + ", u." + nameof(UserIdentity.FirstName) + ", u." + "select u." + nameof(UserIdentity.Id) + ", u." + nameof(UserIdentity.FirstName) + ", u." +
@ -38,6 +63,9 @@ namespace SQLiteRepository
public const string GET_UNASSIGNED_CARD_LIST = public const string GET_UNASSIGNED_CARD_LIST =
"select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.UserId_FK) + "=?"; "select * from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.UserId_FK) + "=?";
public const string CLEAR_UNASSIGNED_CARDS =
"delete from " + nameof(CardUniqueId) + " where " + nameof(CardUniqueId.UserId_FK) + "=?";
public const string UPDATE_CARD_USER_ID = public const string UPDATE_CARD_USER_ID =
"update " + nameof(CardUniqueId) + " set " + nameof(CardUniqueId.UserId_FK) + "=? where " + "update " + nameof(CardUniqueId) + " set " + nameof(CardUniqueId.UserId_FK) + "=? where " +
nameof(CardUniqueId.Id) + "=?"; nameof(CardUniqueId.Id) + "=?";

View File

@ -8,6 +8,7 @@ using System.Reflection;
using Interfaces; using Interfaces;
using SQLite.Net; using SQLite.Net;
using SQLite.Net.Platform.Win32; using SQLite.Net.Platform.Win32;
using SQLiteRepository.Properties;
namespace SQLiteRepository namespace SQLiteRepository
{ {
@ -32,7 +33,68 @@ namespace SQLiteRepository
_connection.CreateTable<TimeLogDb>(); _connection.CreateTable<TimeLogDb>();
_connection.CreateTable<GroupDb>(); _connection.CreateTable<GroupDb>();
_connection.CreateTable<UserGroupJoinDb>(); _connection.CreateTable<UserGroupJoinDb>();
_connection.CreateTable<DbVersion>();
_logger.Trace("Initialised SQLite Repository"); _logger.Trace("Initialised SQLite Repository");
_logger.Trace("Checking For Upgrades");
CheckForDbUpgrade();
}
private void CheckForDbUpgrade()
{
var data = _connection.Query<DbVersion>("select * from DbVersion");
if (!data.Any())
{
//Pre-Upgrade database, need upgrading
_logger.Trace("Pre version 0.2 RC database found, performing update..");
ExecuteUpgradeFromVersion("0.1"); //execute 0.2 upgrade scripts onwards.
}
else
{
var installedVersion = new Version(data.First().VersionNumber);
var currentVersion = new Version(AssemblyInfo.ASSEMBLY_VERSION);
if (currentVersion.CompareTo(installedVersion) > 0) //greater than 0 - current version is newer
{
_logger.Trace("Installed Database Version: {0} is older than current version {1}");
ExecuteUpgradeFromVersion(installedVersion.ToString());
}
}
}
private void ExecuteUpgradeFromVersion(string installedVersion)
{
const string PREFIX = "SQLiteRepository.UpgradeScripts.";
var instVers = new Version(installedVersion);
//so we have established that each script for upgrade will be <version>.sql, so now start at lowest version and work up!
var assembly = Assembly.GetExecutingAssembly();
var upgradeScripts = assembly.GetManifestResourceNames()
.Where(x=>x.StartsWith(PREFIX))
.OrderBy(x=>new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, ""))))
.Where(x =>
{
var scriptVersion = new Version(Path.GetFileNameWithoutExtension(x.Replace(PREFIX, ""))).CompareTo(instVers);
return scriptVersion > 0;
}
)
.ToList();
//now have an ordered list of upgrade script files, so execute in order!
foreach (var upgradeScript in upgradeScripts)
{
using (var stream = assembly.GetManifestResourceStream(upgradeScript))
using(var str = new StreamReader(stream))
{
var script = str.ReadToEnd();
_logger.Trace("Executing upgrade script with name: {0}", upgradeScript);
_connection.Execute(script);
}
}
SetDbVersion(AssemblyInfo.ASSEMBLY_VERSION);
}
private void SetDbVersion(string vers)
{
_connection.DeleteAll<DbVersion>();
_connection.Insert(new DbVersion {VersionNumber = vers});
_logger.Trace("Set Database version to: {0}", vers);
} }
public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1) public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1)
@ -247,6 +309,13 @@ namespace SQLiteRepository
return ret; return ret;
} }
public void ClearUnassignedIdentifiers()
{
_connection.Execute(
SQLiteProcedures.CLEAR_UNASSIGNED_CARDS,
Constants.UNASSIGNED_CARD_USER_ID);
}
//TODO: Check time logs table on update to ensure associated cards/unique identifiers are removed/added as appropriate. //TODO: Check time logs table on update to ensure associated cards/unique identifiers are removed/added as appropriate.
public OperationResponse UpdateUser(User user, out int userIdResult) public OperationResponse UpdateUser(User user, out int userIdResult)
{ {
@ -487,7 +556,7 @@ namespace SQLiteRepository
{ {
//TODO: I would probably prefer to do this manually.... //TODO: I would probably prefer to do this manually....
var resp = _connection.Query<GroupDb>("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id); var resp = _connection.Query<GroupDb>("update GroupDb set GroupName=? where GroupId=?", group.Name, group.Id);
//var resp = _connection.Update(groupDb);
return OperationResponse.UPDATED; return OperationResponse.UPDATED;
} }
@ -505,6 +574,8 @@ namespace SQLiteRepository
if (!query.Any()) if (!query.Any())
return OperationResponse.FAILED; return OperationResponse.FAILED;
UpdateExistingLogDirections(log);
_connection.ExecuteScalar<TimeLogDb>("delete from TimeLogDb where Id=?", log.Id); _connection.ExecuteScalar<TimeLogDb>("delete from TimeLogDb where Id=?", log.Id);
return OperationResponse.DELETED; return OperationResponse.DELETED;
@ -514,6 +585,8 @@ namespace SQLiteRepository
{ {
var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); var calendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime);
var year = log.EventTime.Year; var year = log.EventTime.Year;
log.CalendarWeek = calendarWeek;
log.Year = year;
var dbLog = new TimeLogDb var dbLog = new TimeLogDb
{ {
SwipeEventDateTime = log.EventTime, SwipeEventDateTime = log.EventTime,
@ -524,6 +597,10 @@ namespace SQLiteRepository
UserId_FK = log.UserId, UserId_FK = log.UserId,
IdentifierId = ConvertSourceToIdentifierId(log.Source), IdentifierId = ConvertSourceToIdentifierId(log.Source),
}; };
#region update in/out directions for manual logs.
UpdateExistingLogDirections(log);
#endregion
//and now insert the new log.
_connection.Insert(dbLog); _connection.Insert(dbLog);
return OperationResponse.CREATED; return OperationResponse.CREATED;
} }
@ -720,10 +797,7 @@ namespace SQLiteRepository
else else
{ {
// we have a time log from today already, so just do the opposite of what we last did! // we have a time log from today already, so just do the opposite of what we last did!
if (lastEntry.Direction == LogDirectionDb.IN) logDirection = InvertLogDirectionDb(lastEntry.Direction);
logDirection = LogDirectionDb.OUT;
else if (lastEntry.Direction == LogDirectionDb.OUT)
logDirection = LogDirectionDb.IN;
} }
} }
else else
@ -809,5 +883,47 @@ namespace SQLiteRepository
IsContractor = user.IsContractor IsContractor = user.IsContractor
}; };
} }
private void UpdateExistingLogDirections(TimeLog log)
{
//need to make this generic so that both create and delete will update the log directions.. but ARGH.
//if you want to use this as a delete, you would delete and call this, if you want to create, call this then create.
//should look at how to improve this method so that it will compensate for if the log hasnt been deleted yet, but ott?
var weekLogs = GetTimeLogList(log.UserId, log.CalendarWeek, log.Year);
//Get the same logs of the day that the log has been entered for
var todaysLogs = weekLogs.FirstOrDefault(x => x.Day == log.EventTime.DayOfWeek);
if (todaysLogs != null)
{
//Get the days logs that are after the manually created date
var logs = todaysLogs.Logs.Where(x => x.EventTime.CompareTo(log.EventTime) >= 0).OrderBy(x => x.EventTime).ToList();
//Update each log with the inverse progressively
var currentlogDirection = log.Direction;
for (var i = 0; i < logs.Count; i++)
{
logs[i].Direction = InvertLogDirection(currentlogDirection);
UpdateLog(logs[i]);
currentlogDirection = logs[i].Direction;
}
}
}
private LogDirectionDb InvertLogDirectionDb(LogDirectionDb direction)
{
return (LogDirectionDb) (int) InvertLogDirection((LogDirection) (int) direction);
}
private LogDirection InvertLogDirection(LogDirection direction)
{
switch (direction)
{
case LogDirection.IN:
return LogDirection.OUT;
case LogDirection.OUT:
return LogDirection.IN;
default:
return LogDirection.UNKNOWN;
}
}
} }
} }

View File

@ -78,6 +78,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CardUniqueId.cs" /> <Compile Include="CardUniqueId.cs" />
<Compile Include="DBVersion.cs" />
<Compile Include="GroupDb.cs" /> <Compile Include="GroupDb.cs" />
<Compile Include="LogSourceDb.cs" /> <Compile Include="LogSourceDb.cs" />
<Compile Include="SQLiteRepository.cs" /> <Compile Include="SQLiteRepository.cs" />
@ -102,6 +103,9 @@
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UpgradeScripts\0.2.sql" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" /> <Import Project="..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.104.0\build\net451\System.Data.SQLite.Core.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@ -0,0 +1 @@
insert into GroupDb values ((select max(groupId) from GroupDb)+1,'Archived',0)

View File

@ -3,7 +3,7 @@
<Product Id="{52F7296E-1C7E-497E-9B25-4FE715D7AD1C}" <Product Id="{52F7296E-1C7E-497E-9B25-4FE715D7AD1C}"
Name="DataCentreHostInstaller" Name="DataCentreHostInstaller"
Language="1033" Language="1033"
Version="0.1.5.0" Version="0.2.1.0"
Manufacturer="ChrisWatts" Manufacturer="ChrisWatts"
UpgradeCode="7282166b-691e-4caa-9a80-f348b8e5ffef"> UpgradeCode="7282166b-691e-4caa-9a80-f348b8e5ffef">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

View File

@ -93,6 +93,12 @@
KeyPath="yes" KeyPath="yes"
Checksum="yes"/> Checksum="yes"/>
</Component> </Component>
<Component Id="KnockoutContextMenuJs" Guid="{C48C75F7-E4C3-45D7-8041-0BF7A8EEC077}">
<File Id="knockout.contextmenu.js"
Source="$(var.WindowsDataCenter.TargetDir)www\js\knockout.contextmenu.js"
KeyPath="yes"
Checksum="yes"/>
</Component>
</ComponentGroup> </ComponentGroup>
<ComponentGroup Id="WWWStaticFonts" Directory="STATICWWWFONTS" > <ComponentGroup Id="WWWStaticFonts" Directory="STATICWWWFONTS" >
@ -177,6 +183,12 @@
KeyPath="yes" KeyPath="yes"
Checksum="yes"/> Checksum="yes"/>
</Component> </Component>
<Component Id="KnockoutContextMenuCss" Guid="{D5C5C0F3-500C-419C-A327-5342AFF08204}">
<File Id="knockout.contextmenu.css"
Source="$(var.WindowsDataCenter.TargetDir)www\css\knockout.contextmenu.css"
KeyPath="yes"
Checksum="yes" />
</Component>
</ComponentGroup> </ComponentGroup>
</Fragment> </Fragment>

View File

@ -5,7 +5,7 @@
<add key="DefaultPageSize" value="20" /> <add key="DefaultPageSize" value="20" />
<add key="WebsiteHttpPort" value="8800"/> <add key="WebsiteHttpPort" value="8800"/>
<add key="SwipeTimeGap" value="3" /> <add key="SwipeTimeGap" value="3" />
<add key="BugSubmissionEmailAddress" value="incoming+WattsC/FlexiTimeTrackerTool+24qrefn8e1urhl4iqct7we2jl@gitlab.com"/> <add key="BugSubmissionEmailAddress" value="incoming+WattsC/FlexiTimeTrackerTool@incoming.gitlab.com"/>
</appSettings> </appSettings>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />

View File

@ -27,5 +27,15 @@ namespace WindowsDataCenter
_logger.Trace("Call to GetUnassignedCards, returning {0} items", unassignedCards.data.Count); _logger.Trace("Call to GetUnassignedCards, returning {0} items", unassignedCards.data.Count);
return Ok(unassignedCards); return Ok(unassignedCards);
} }
[HttpDelete]
[Route("unassigned")]
[CacheControl(MaxAge = 0)]
public IHttpActionResult ClearUnassignedCards()
{
_repo.ClearUnassignedIdentifiers();
_logger.Trace("Call to ClearUnassignedCards, removed all identifiers.");
return Ok();
}
} }
} }

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.5.0")] [assembly: AssemblyVersion("0.2.1.0")]
[assembly: AssemblyFileVersion("0.1.5.0")] [assembly: AssemblyFileVersion("0.2.1.0")]

View File

@ -77,7 +77,33 @@
</form> </form>
</div> </div>
</div> </div>
<div id="CardManagement" class="container">
<div class="row">
<h2 class="col-md-4">Unassigned Cards</h2>
<button class="col-md-1 btn btn-default pull-right" style="margin-top: 20px;" data-bind="click: $root.clearUnassignedCards">
<span class="glyphicon glyphicon-trash"></span>
</button>
</div>
<div class="row">
<div data-bind="with: unassignedCardList">
<table class="table table-striped">
<thead>
<tr>
<th>Card Unique Id</th>
<th>Last Used Date</th>
</tr>
</thead>
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: UniqueId"></td>
<td data-bind="text: $root.convertToDisplayDateTime(LastUsed)"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script src="Helpers.min.js" type="text/javascript"></script> <script src="Helpers.min.js" type="text/javascript"></script>
<script src="admin.min.js" type="text/javascript"></script> <script src="admin.js" type="text/javascript"></script>
</body> </body>
</html> </html>

View File

@ -2,6 +2,7 @@
var self = this; var self = this;
self.groupsList = ko.observable(null); self.groupsList = ko.observable(null);
self.groupEditItem = ko.observable(null); self.groupEditItem = ko.observable(null);
self.unassignedCardList = ko.observable(null);
self.helpers = new Helpers(); self.helpers = new Helpers();
self.uiPages = { self.uiPages = {
overview: "overview", overview: "overview",
@ -11,7 +12,9 @@
self.apiEndpoints = { self.apiEndpoints = {
deleteGroups:"/api/groups/delete", deleteGroups:"/api/groups/delete",
getGroups: "/api/groups", getGroups: "/api/groups",
editGroup: "/api/groups/edit" editGroup: "/api/groups/edit",
getUnassignedCards: "/api/cards/unassigned",
clearUnassignedCards: "/api/cards/unassigned"
}; };
self.clearGroupForm = function () { self.clearGroupForm = function () {
self.helpers.goToMenuOption(self.uiPages.group); self.helpers.goToMenuOption(self.uiPages.group);
@ -69,10 +72,48 @@
var errorObj = self.helpers.processRequestFailure(resp, status, error); var errorObj = self.helpers.processRequestFailure(resp, status, error);
}); });
}; };
self.getUnassignedCardData = function() {
var url = self.helpers.createRequestUrl(self.apiEndpoints.getUnassignedCards, null, false);
$.getJSON(url,
function(res) {
self.unassignedCardList(res);
}).fail(function(resp, status, error) {
console.log("error - getUnassignedCards");
var errorObj = self.helpers.processRequestFailure(resp, status, error);
});
};
self.clearUnassignedCards = function() {
var url = self.helpers.createRequestUrl(self.apiEndpoints.clearUnassignedCards, null, false);
$.ajax({
type: "DELETE",
url: url,
data: "",
success: function() {
self.getUnassignedCardData(); //update
},
error: function(jqxhr, status, error) {
console.log("error - clearUnassignedCards");
var errorObj = self.helpers.processRequestFailure(resp, status, error);
}
});
};
self.padNumber = function (number) {
return (number < 10 ? "0" : "") + number;
};
self.convertToDisplayDateTime = function (dateValue) {
var date = new Date(dateValue); // dd MM YY HH:mm:ss e.g.: 01 Mar 17 17:34:02
return date.getDate() + " "
+ date.toLocaleString("en-us", { month: "long" }) + " "
+ (date.getYear()-100) + " "
+ self.padNumber(date.getHours()) + ":"
+ self.padNumber(date.getMinutes()) + ":"
+ self.padNumber(date.getSeconds());
};
Sammy(function () { Sammy(function () {
this.disable_push_state = true; this.disable_push_state = true;
this.get("#overview", function () { this.get("#overview", function () {
self.getGroups(); self.getGroups();
self.getUnassignedCardData();
}); });
this.post("#editgroup", function () { this.post("#editgroup", function () {
self.submitGroupEdit(self.groupEditItem()); self.submitGroupEdit(self.groupEditItem());

View File

@ -1 +1 @@
function AdminVM(){var n=this;n.groupsList=ko.observable(null);n.groupEditItem=ko.observable(null);n.helpers=new Helpers;n.uiPages={overview:"overview",group:"groups",home:function(){return this.overview}};n.apiEndpoints={deleteGroups:"/api/groups/delete",getGroups:"/api/groups",editGroup:"/api/groups/edit"};n.clearGroupForm=function(){n.helpers.goToMenuOption(n.uiPages.group);n.groupEditItem(null)};n.hideGroupForm=function(){n.groupEditItem(null)};n.newGroupForm=function(){n.groupEditItem({Id:-1,Name:""});n.helpers.goToMenuOption(n.uiPages.group)};n.groupFormHidden=ko.computed(function(){return n.groupEditItem()==null},n);n.editGroupClick=function(t){n.helpers.goToMenuOption(n.uiPages.group);n.groupEditItem(t)};n.getGroups=function(){var t=n.helpers.createRequestUrl(n.apiEndpoints.getGroups,null,!1);$.getJSON(t,function(t){n.groupsList(t)}).fail(function(t,i,r){console.log("error - getGroups");var u=n.helpers.processRequestFailure(t,i,r)})};n.deleteGroup=function(t){var i=n.helpers.createRequestUrl(n.apiEndpoints.deleteGroups,[{key:"groupId",value:t}],!1,!1);$.ajax({url:i,type:"DELETE",success:function(){console.log("deleted "+t);n.hideGroupForm();n.helpers.goToMenuOption(n.uiPages.home())}});console.log("delete: "+t)};n.submitGroupEdit=function(t){var i=n.helpers.createRequestUrl(n.apiEndpoints.editGroup,null,!1);$.post(i,t,function(){},"json").done(function(){n.groupEditItem(null);n.helpers.goToMenuOption(n.uiPages.home())}).fail(function(t,i,r){n.helpers.goToMenuOption(n.uiPages.home());var u=n.helpers.processRequestFailure(t,i,r)})};Sammy(function(){this.disable_push_state=!0;this.get("#overview",function(){n.getGroups()});this.post("#editgroup",function(){return n.submitGroupEdit(n.groupEditItem()),!1});this.get("",function(){this.app.runRoute("get","#"+n.uiPages.home())})}).run()}ko.applyBindings(new AdminVM) function AdminVM(){var n=this;n.groupsList=ko.observable(null);n.groupEditItem=ko.observable(null);n.unassignedCardList=ko.observable(null);n.helpers=new Helpers;n.uiPages={overview:"overview",group:"groups",home:function(){return this.overview}};n.apiEndpoints={deleteGroups:"/api/groups/delete",getGroups:"/api/groups",editGroup:"/api/groups/edit",getUnassignedCards:"/api/cards/unassigned",clearUnassignedCards:"/api/cards/unassigned"};n.clearGroupForm=function(){n.helpers.goToMenuOption(n.uiPages.group);n.groupEditItem(null)};n.hideGroupForm=function(){n.groupEditItem(null)};n.newGroupForm=function(){n.groupEditItem({Id:-1,Name:""});n.helpers.goToMenuOption(n.uiPages.group)};n.groupFormHidden=ko.computed(function(){return n.groupEditItem()==null},n);n.editGroupClick=function(t){n.helpers.goToMenuOption(n.uiPages.group);n.groupEditItem(t)};n.getGroups=function(){var t=n.helpers.createRequestUrl(n.apiEndpoints.getGroups,null,!1);$.getJSON(t,function(t){n.groupsList(t)}).fail(function(t,i,r){console.log("error - getGroups");var u=n.helpers.processRequestFailure(t,i,r)})};n.deleteGroup=function(t){var i=n.helpers.createRequestUrl(n.apiEndpoints.deleteGroups,[{key:"groupId",value:t}],!1,!1);$.ajax({url:i,type:"DELETE",success:function(){console.log("deleted "+t);n.hideGroupForm();n.helpers.goToMenuOption(n.uiPages.home())}});console.log("delete: "+t)};n.submitGroupEdit=function(t){var i=n.helpers.createRequestUrl(n.apiEndpoints.editGroup,null,!1);$.post(i,t,function(){},"json").done(function(){n.groupEditItem(null);n.helpers.goToMenuOption(n.uiPages.home())}).fail(function(t,i,r){n.helpers.goToMenuOption(n.uiPages.home());var u=n.helpers.processRequestFailure(t,i,r)})};n.getUnassignedCardData=function(){var t=n.helpers.createRequestUrl(n.apiEndpoints.getUnassignedCards,null,!1);$.getJSON(t,function(t){n.unassignedCardList(t)}).fail(function(t,i,r){console.log("error - getUnassignedCards");var u=n.helpers.processRequestFailure(t,i,r)})};n.clearUnassignedCards=function(){var t=n.helpers.createRequestUrl(n.apiEndpoints.clearUnassignedCards,null,!1);$.ajax({type:"DELETE",url:t,data:"",success:function(){n.getUnassignedCardData()},error:function(t,i,r){console.log("error - clearUnassignedCards");var u=n.helpers.processRequestFailure(resp,i,r)}})};n.padNumber=function(n){return(n<10?"0":"")+n};n.convertToDisplayDateTime=function(t){var i=new Date(t);return i.getDate()+" "+i.toLocaleString("en-us",{month:"long"})+" "+(i.getYear()-100)+" "+n.padNumber(i.getHours())+":"+n.padNumber(i.getMinutes())+":"+n.padNumber(i.getSeconds())};Sammy(function(){this.disable_push_state=!0;this.get("#overview",function(){n.getGroups();n.getUnassignedCardData()});this.post("#editgroup",function(){return n.submitGroupEdit(n.groupEditItem()),!1});this.get("",function(){this.app.runRoute("get","#"+n.uiPages.home())})}).run()}ko.applyBindings(new AdminVM)

View File

@ -10,14 +10,14 @@
<link href="css/knockout.contextmenu.css" rel="stylesheet" type="text/css"/> <link href="css/knockout.contextmenu.css" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js" type="text/javascript"></script>
<script src="js/knockout.contextmenu.js" type="text/javascript"></script> <script src="js/knockout.contextmenu.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sammy.js/0.7.6/sammy.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/sammy.js/0.7.6/sammy.js" type="text/javascript"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/momentjs/2.10.6/moment.min.js"></script> <script src="https://cdn.jsdelivr.net/momentjs/2.10.6/moment.min.js" type="text/javascript"></script>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.1/js/bootstrap-datepicker.js"></script>--> <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.6.1/js/bootstrap-datepicker.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js" type="text/javascript"></script>
</head> </head>
<body data-bind="css: {footerBody: errorData() !== null}"> <body data-bind="css: {footerBody: errorData() !== null}">
<nav class="navbar navbar-default"> <nav class="navbar navbar-default">
@ -254,6 +254,9 @@
<div class="col-md-3 col-xs-12"> <div class="col-md-3 col-xs-12">
<div id="datePickerContainer"> <div id="datePickerContainer">
<div id="weeklyDatePicker"></div> <div id="weeklyDatePicker"></div>
<div class="text-center">
<button class="btn btn-default form-control" type="button" id="manualTodayButton" data-bind="click: $root.goToToday">Today</button>
</div>
</div> </div>
</div> </div>
<div class="col-md-9 col-xs-12 well"> <div class="col-md-9 col-xs-12 well">
@ -271,16 +274,18 @@
</thead> </thead>
<tbody> <tbody>
<!-- ko foreach: TimeLogs--> <!-- ko foreach: TimeLogs-->
<tr> <!-- ko if: !((DayOfWeek === "Saturday" || DayOfWeek=== "Sunday") && LogCount===0)-->
<td class="valign" data-bind="text: DayOfWeek, contextMenu: $root.createContextMenu"></td> <tr>
<!-- ko foreach: Logs --> <td class="valign" data-bind="text: DayOfWeek, contextMenu: $root.createContextMenu"></td>
<td class="valign" data-bind="text: $root.convertToDisplayTime(EventTime), contextMenu: $root.editContextMenu"></td> <!-- ko foreach: Logs -->
<td class="valign" data-bind="text: $root.convertToDisplayTime(EventTime), contextMenu: $root.editContextMenu"></td>
<!-- /ko -->
<!-- ko foreach: new Array($root.correctLogOffset($parent.MaxDailyLogCount)-LogCount)-->
<td class="valign" data-bind="contextMenu: $root.createContextMenu"></td>
<!-- /ko -->
<td class="valign" data-bind="text: $root.convertToHours(DailyTotal), contextMenu: $root.createContextMenu"></td>
</tr>
<!-- /ko --> <!-- /ko -->
<!-- ko foreach: new Array($root.correctLogOffset($parent.MaxDailyLogCount)-LogCount)-->
<td class="valign" data-bind="contextMenu: $root.createContextMenu"></td>
<!-- /ko -->
<td class="valign" data-bind="text: $root.convertToHours(DailyTotal), contextMenu: $root.createContextMenu"></td>
</tr>
<!-- /ko --> <!-- /ko -->
<tr> <tr>
<td class="valign" data-bind="attr:{colspan: $root.correctLogOffset(MaxDailyLogCount)+1}, contextMenu: $root.createContextMenu">Weekly Total</td> <td class="valign" data-bind="attr:{colspan: $root.correctLogOffset(MaxDailyLogCount)+1}, contextMenu: $root.createContextMenu">Weekly Total</td>
@ -332,20 +337,16 @@
<button type="button" class="close" data-dismiss="modal">&times;</button> <button type="button" class="close" data-dismiss="modal">&times;</button>
<h4>MANUAL EDIT</h4> <h4>MANUAL EDIT</h4>
</div> </div>
<div class="modal-body" style="height: 300px;"> <div class="modal-body" style="height: 400px;">
<form action="#manualLog" method="post" class="form-group"> <form action="#manualLog" method="post" class="form-group">
<input type="hidden" name="Id" data-bind="value: Id"/> <input type="hidden" name="Id" data-bind="value: Id"/>
<div class="form-group"> <div class="form-group">
<div class="input-group date" id="datetimepicker1"> <div id="datetimepicker1"></div>
<input type="text" class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div> </div>
<br/>
<div> <div>
<div class="input-group"> <div class="input-group col-md-offset-1">
<select data-bind="options: $root.possibleLogDirections, <select class="form-control" data-bind="options: $root.possibleLogDirections,
optionsText: function(item) { return item.Text }, optionsText: function(item) { return item.Text },
optionsValue: function(item){ return item.value }, optionsValue: function(item){ return item.value },
value: Direction, value: Direction,
@ -353,8 +354,10 @@
</div> </div>
</div> </div>
<br/> <br/>
<button type="submit" class="btn btn-primary">Submit</button> <div class="pull-right">
<button type="button" class="btn btn-secondary close" data-dismiss="modal">Cancel</button> <button type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@ -374,6 +377,6 @@
</div> </div>
</footer> </footer>
<script src="Helpers.js" type="text/javascript"></script> <script src="Helpers.js" type="text/javascript"></script>
<script src="spa.js" type="text/javascript"></script> <script src="spa.js" type="text/javascript"></script>
</body> </body>
</html> </html>

View File

@ -29,4 +29,8 @@
.footerBody { .footerBody {
/* Margin bottom by footer height */ /* Margin bottom by footer height */
margin-bottom: 132px; margin-bottom: 132px;
}
/*Make the cursor a pointer for all hyperlinks*/
a:hover {
cursor: pointer;
} }

View File

@ -52,6 +52,11 @@
} }
location.hash = url; location.hash = url;
}; };
self.goToToday = function() {
self.goToTimeLogs(self.chosenTimeLogUserId,
null,
null);
};
self.assignErrorObject = function(errCode, errMessage, errorSource) { self.assignErrorObject = function(errCode, errMessage, errorSource) {
var errDat = { var errDat = {
errorCode: errCode, errorCode: errCode,
@ -176,9 +181,9 @@
} }
moment.locale("en", { week: { dow: 1 } }); moment.locale("en", { week: { dow: 1 } });
$("#weeklyDatePicker").datetimepicker({ $("#weeklyDatePicker").datetimepicker({
//showTodayButton: true,
format: "DD/MM/YYYY", format: "DD/MM/YYYY",
inline: true, inline: true,
showTodayButton: true,
calendarWeeks: true, calendarWeeks: true,
maxDate: "now", maxDate: "now",
date: selectedDate date: selectedDate
@ -336,10 +341,10 @@
$.post(url, newLog, function () { $.post(url, newLog, function () {
}, "json") }, "json")
.done(function () { .done(function () {
self.manualLog(null); self.manualLog(null);
$('#manualLogDialog').modal("hide"); $('#manualLogDialog').modal("hide");
self.goToMenuOption(self.uiPages.home()); location.reload(); //stay on this users logs page, but just reload the timelogs.
}) })
.fail(function (resp, status, error) { .fail(function (resp, status, error) {
var errObj = self.helpers.processRequestFailure(resp, status, error); var errObj = self.helpers.processRequestFailure(resp, status, error);
self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "createManualLog"); self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "createManualLog");
@ -355,7 +360,7 @@
data: logToDelete, data: logToDelete,
success: function (result) { success: function (result) {
console.log("successfully deleted .." + result); console.log("successfully deleted .." + result);
self.goToMenuOption(self.uiPages.home()); location.reload(); //stay on this page but just reload the timelogs.
} }
}); });
}; };
@ -369,16 +374,11 @@
]); ]);
function editlog (data) { function editlog (data) {
self.manualLog(data); self.manualLog(data);
$('#manualLogDialog').modal("show"); $("#manualLogDialog").modal("show");
$('#datetimepicker1').datetimepicker({ self.initialiseManualLogDateTimePicker(data.EventTime);
format: "YYYY-DD-MM HH:mm:ss",
date: new Date(data.EventTime),
minDate: moment(new Date(data.EventTime)).startOf('week'),
maxDate: moment(new Date(data.EventTime)).endOf('week')
});
self.assignUpdateHandler(); self.assignUpdateHandler();
}; };
function createlog(data) { function createlog(data, event) {
self.manualLog({ self.manualLog({
CalendarWeek:-1, CalendarWeek:-1,
Direction:-1, Direction:-1,
@ -389,11 +389,7 @@
Year: 0 Year: 0
}); });
$('#manualLogDialog').modal("show"); $('#manualLogDialog').modal("show");
$('#datetimepicker1').datetimepicker({ self.initialiseManualLogDateTimePicker(self.selectedTimeLogDate());
format: "YYYY-DD-MM HH:mm:ss",
minDate: moment(self.selectedTimeLogDate()).startOf("week"),
maxDate: moment(self.selectedTimeLogDate()).endOf("week")
});
self.assignUpdateHandler(); self.assignUpdateHandler();
}; };
function deleteLog(data) { function deleteLog(data) {
@ -401,6 +397,17 @@
self.deleteManualLog(data); self.deleteManualLog(data);
} }
}; };
self.initialiseManualLogDateTimePicker = function (date) {
$('#datetimepicker1')
.datetimepicker({
format: "YYYY-DD-MM HH:mm:ss",
date: new Date(date),
inline:true,
sideBySide:true,
minDate: moment(date).startOf("week"),
maxDate: moment(date).endOf("week")
});
};
Sammy(function () { Sammy(function () {
this.get("#users", function () { this.get("#users", function () {
var query = this.params.query; var query = this.params.query;
@ -463,7 +470,7 @@
"IsContractor": false "IsContractor": false
}); });
self.getGroups(function(data) { self.getGroups(function(data) {
self.chosenUserDetails().Groups = data; self.chosenUserDetails().Groups = data.Groups;
self.chosenUserDetails.valueHasMutated(); self.chosenUserDetails.valueHasMutated();
}); });
self.getUnassignedCardData(); self.getUnassignedCardData();

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.4.0")] [assembly: AssemblyVersion("0.2.1.0")]
[assembly: AssemblyFileVersion("0.1.4.0")] [assembly: AssemblyFileVersion("0.2.1.0")]