From 4a394d3ff7d87d8a9968088214ff484e22c500a8 Mon Sep 17 00:00:00 2001 From: Andy Howard Date: Wed, 16 Feb 2022 10:47:45 +0000 Subject: [PATCH] v1.2.3.1 --- .../Base/Consoles/SaxActivityConsole_Web.cs | 41 +- .../Base/Consoles/SaxDataCacheConsole_Web.cs | 126 ++-- .../Consoles/SaxSessionDataConsole_Web.cs | 37 ++ .../Base/Controllers/SaxController_Base.cs | 19 +- SaxoniteCoreWeb/Base/Models/SaxModel_Base.cs | 24 +- .../Consoles/SaxConsoleHelper_WebAdmin.cs | 8 + .../Controllers/SecurityController-Ajax.cs | 279 +++++---- .../Admin/Controllers/UserController-Ajax.cs | 381 ++++++------ .../Controllers/UsersOnlineController-Ajax.cs | 190 ++++++ .../Controllers/UsersOnlineController.cs | 45 ++ .../Admin/Models/Model_User.cs | 165 ++++-- .../Admin/Models/Model_UsersOnline.cs | 54 ++ .../Admin/Models/SaxModel_Admin.cs | 22 + .../Localisation/SaxAdminProperties.resx | 9 + .../Panel-OrganisationDialog.cshtml | 4 +- .../Views/Shared/_Layout.cshtml | 113 ++-- SaxoniteCoreWebAdmin/Views/User/Index.cshtml | 8 +- .../Views/User/Panel-UserDialog.cshtml | 120 ++-- .../Views/UsersOnline/Index.cshtml | 144 +++++ SaxoniteCoreWebAdmin/wwwroot/js/sax-main.js | 34 +- SaxoniteCoreWebAdmin/wwwroot/js/sax-user.js | 109 +++- .../wwwroot/js/sax-usr-onl.js | 316 ++++++++++ .../Consoles/SaxConsoleHelper_WebOnline.cs | 14 + .../Consoles/SaxDataCacheConsole_Online.cs | 1 + .../Consoles/SaxSessionDataConsole_Online.cs | 119 ++++ .../SearchController-Ajax-Organisations.cs | 10 +- .../SearchController-Ajax-Users.cs | 17 +- .../Controllers/SecurityController-Ajax.cs | 1 + .../Online/Models/Model_Article.cs | 3 +- .../SaxoniteCoreWebOnline.csproj | 1 + .../Views/Article/Index.cshtml | 1 + .../Views/Article/Panel-ArticleDialog.cshtml | 2 +- .../Views/Common/OrganisationControls.cshtml | 4 +- .../Views/Common/ResultsPaging.cshtml | 4 +- .../Views/Search/Organisations.cshtml | 2 +- .../Views/Search/Panel-UserDialog.cshtml | 5 + .../Views/Shared/_Layout.cshtml | 13 +- SaxoniteCoreWebOnline/wwwroot/js/sax-art.js | 6 +- SaxoniteCoreWebOnline/wwwroot/js/sax-main.js | 39 +- .../wwwroot/js/sax-src-usr.js | 2 +- SaxoniteStandardBase/Base/App/Sax-Config.cs | 23 +- SaxoniteStandardBase/Base/App/Sax-Version.cs | 7 +- SaxoniteStandardBase/Base/App/Sax.cs | 1 + .../Base/App/SaxDbDfn_Core.cs | 96 +-- SaxoniteStandardBase/Base/App/SaxInternals.cs | 6 +- .../Base/App/SaxSystem-Statics.cs | 1 + .../Base/Bus/SaxOrganisation.cs | 1 + .../Base/Bus/SaxUserAccount.cs | 2 +- .../Base/Bus/SaxUserOnline.cs | 170 ++++++ .../Base/Consoles/SaxConsoleHelper.cs | 4 + .../Base/Consoles/SaxSessionDataConsole.cs | 56 ++ .../Base/Mgr/SaxAccountMgr-UserOnline.cs | 120 ++++ .../Dbas/Bus/SaxUserOnline_Dbas.cs | 32 + .../Dbas/Internal/ActivityIndex_Db.cs | 2 +- .../Dbas/Internal/Article_Db.cs | 2 +- .../Dbas/Internal/ItemAddress_Db.cs | 2 +- .../Dbas/Internal/ItemImage_Db.cs | 2 +- .../Dbas/Internal/ItemSport_Db.cs | 2 +- .../Dbas/Internal/OrganisationEvent_Db.cs | 2 +- .../Internal/OrganisationOpportunity_Db.cs | 2 +- .../Dbas/Internal/Organisation_Db.cs | 2 +- .../Dbas/Internal/Region_Db.cs | 2 +- .../Dbas/Internal/Sport_Db.cs | 2 +- .../Dbas/Internal/UserAccount_Db.cs | 2 +- .../Dbas/Internal/UserActivity_Db.cs | 2 +- .../Dbas/Internal/UserOnline_Db.cs | 148 +++++ .../Dbas/Internal/UserOrganisation_Db.cs | 2 +- .../Dbas/Internal/UserRole_Db.cs | 2 +- .../Dbas/Internal/UserSession_Db.cs | 2 +- .../Dbas/Internal/UserSportExperience_Db.cs | 2 +- .../Dbas/Internal/UserSport_Db.cs | 2 +- .../Mgr/SaxAccountMgr_Dbas-Organisation.cs | 2 + .../Mgr/SaxAccountMgr_Dbas-UserAccount.cs | 2 +- .../Dbas/Mgr/SaxAccountMgr_Dbas-UserOnline.cs | 552 ++++++++++++++++++ .../Resources/Database/SaxDbDfn_Core.xml | 69 ++- 75 files changed, 3127 insertions(+), 691 deletions(-) create mode 100644 SaxoniteCoreWeb/Base/Consoles/SaxSessionDataConsole_Web.cs create mode 100644 SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController-Ajax.cs create mode 100644 SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController.cs create mode 100644 SaxoniteCoreWebAdmin/Admin/Models/Model_UsersOnline.cs create mode 100644 SaxoniteCoreWebAdmin/Views/UsersOnline/Index.cshtml create mode 100644 SaxoniteCoreWebAdmin/wwwroot/js/sax-usr-onl.js create mode 100644 SaxoniteCoreWebOnline/Online/Consoles/SaxSessionDataConsole_Online.cs create mode 100644 SaxoniteStandardBase/Base/Bus/SaxUserOnline.cs create mode 100644 SaxoniteStandardBase/Base/Consoles/SaxSessionDataConsole.cs create mode 100644 SaxoniteStandardBase/Base/Mgr/SaxAccountMgr-UserOnline.cs create mode 100644 SaxoniteStandardDbas/Dbas/Bus/SaxUserOnline_Dbas.cs create mode 100644 SaxoniteStandardDbas/Dbas/Internal/UserOnline_Db.cs create mode 100644 SaxoniteStandardDbas/Dbas/Mgr/SaxAccountMgr_Dbas-UserOnline.cs diff --git a/SaxoniteCoreWeb/Base/Consoles/SaxActivityConsole_Web.cs b/SaxoniteCoreWeb/Base/Consoles/SaxActivityConsole_Web.cs index 0523fb5..b681441 100644 --- a/SaxoniteCoreWeb/Base/Consoles/SaxActivityConsole_Web.cs +++ b/SaxoniteCoreWeb/Base/Consoles/SaxActivityConsole_Web.cs @@ -20,7 +20,6 @@ namespace Saxonite.Net.Core.Web.Base.Consoles { //------------------------------------------------------------------- // Data members. - private readonly SaxInternals internals; private readonly String alias_ActivityIndex = SaxActivityMgr_Dbas.aliasActivityIndex; private readonly String alias_ActivityIndex_UserActivity = SaxActivityMgr_Dbas.aliasActivityIndex_UserActivity; protected class IndexStatsData @@ -35,13 +34,11 @@ namespace Saxonite.Net.Core.Web.Base.Consoles protected SaxActivityConsole_Web(SaxSessionContext sessionContext) : base(sessionContext) { - internals = new SaxInternals(sessionContext); } protected override void Dispose(bool disposing) { if (disposing) { - internals.Dispose(); } base.Dispose(disposing); } @@ -78,8 +75,8 @@ namespace Saxonite.Net.Core.Web.Base.Consoles if (!context.IsValid()) return results; // Columns to query. - ColumnDfn colItemType = internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemType); - ColumnDfn colItemId = internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemId); + ColumnDfn colItemType = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemType); + ColumnDfn colItemId = Internals.ActivityMgr.GetActivityIndexColumnDfn(SaxActivityIndex.Column.ItemId); String colDataNameEffectiveOn = "EffectiveOn"; // Query the Activity Index. @@ -102,7 +99,7 @@ namespace Saxonite.Net.Core.Web.Base.Consoles prmActivityIndex.Alias_ActivityIndex = alias_ActivityIndex; prmActivityIndex.ResultsPaging.PageNo = 1; prmActivityIndex.ResultsPaging.PageLength = 100; - query.Filters.AddRange(internals.ActivityMgr.GetActivityIndexQueryFilter(prmActivityIndex)); + query.Filters.AddRange(Internals.ActivityMgr.GetActivityIndexQueryFilter(prmActivityIndex)); // First pass, note the activity Item and effective date. List activityDataList = new List(); @@ -382,7 +379,7 @@ namespace Saxonite.Net.Core.Web.Base.Consoles List indexEntryDataList = GetEntriesForIndex(indexData); // Retrieve existing index entries for the item. - List indexEntries = internals.ActivityMgr.GetActivityIndexListForItem(indexData.ItemId); + List indexEntries = Internals.ActivityMgr.GetActivityIndexListForItem(indexData.ItemId); foreach (SaxActivityIndex indexEntry in indexEntries) { // Is the index entry still required. @@ -396,9 +393,9 @@ namespace Saxonite.Net.Core.Web.Base.Consoles if (!indexEntry.EffectiveOn.Equals(indexEntrydata.EffectiveOn)) { indexEntry.EffectiveOn = indexEntrydata.EffectiveOn; - if (internals.ActivityMgr.UpdateActivityIndex(indexEntry, dbUpdateInfo)) + if (Internals.ActivityMgr.UpdateActivityIndex(indexEntry, dbUpdateInfo)) stats.EntriesUpdated++; - else SaLogger.Warn(fn + " Failed to update Activity Index - " + internals.ActivityMgr.ErrorStateMessage); + else SaLogger.Warn(fn + " Failed to update Activity Index - " + Internals.ActivityMgr.ErrorStateMessage); } // Remove the entry from the 'required' list. // Entries remaining in this list will be created. @@ -407,18 +404,18 @@ namespace Saxonite.Net.Core.Web.Base.Consoles else { // Index entry no longer required. - if (internals.ActivityMgr.DeleteActivityIndex(indexEntry, dbUpdateInfo)) + if (Internals.ActivityMgr.DeleteActivityIndex(indexEntry, dbUpdateInfo)) stats.EntriesDeleted++; - else SaLogger.Warn(fn + " Failed to delete Activity Index - " + internals.ActivityMgr.ErrorStateMessage); + else SaLogger.Warn(fn + " Failed to delete Activity Index - " + Internals.ActivityMgr.ErrorStateMessage); } } // The remaining required Index entries will be created. foreach (IndexEntryData indexEntry in indexEntryDataList) { - SaxActivityIndex itemIndexEntry = internals.ActivityMgr.CreateActivityIndex(indexEntry.ItemId, indexEntry.SportId, indexEntry.RegionId, indexEntry.EffectiveOn); - if (internals.ActivityMgr.InsertActivityIndex(itemIndexEntry, dbUpdateInfo)) + SaxActivityIndex itemIndexEntry = Internals.ActivityMgr.CreateActivityIndex(indexEntry.ItemId, indexEntry.SportId, indexEntry.RegionId, indexEntry.EffectiveOn); + if (Internals.ActivityMgr.InsertActivityIndex(itemIndexEntry, dbUpdateInfo)) stats.EntriesInserted++; - else SaLogger.Warn(fn + " Failed to insert Activity Index - " + internals.ActivityMgr.ErrorStateMessage); + else SaLogger.Warn(fn + " Failed to insert Activity Index - " + Internals.ActivityMgr.ErrorStateMessage); } // Log statistics. @@ -506,7 +503,7 @@ namespace Saxonite.Net.Core.Web.Base.Consoles if (!ItemId.IsValid(activityItemId)) return SetErrorState("Activity item id must be specified"); DateTime now = DateTime.Now; - SaxUserActivity userActivity = internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId); + SaxUserActivity userActivity = Internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId); if (userActivity != null) { // Update activity. @@ -515,10 +512,10 @@ namespace Saxonite.Net.Core.Web.Base.Consoles userActivity.ViewedAtFirst = now; userActivity.ViewedAtLast = now; userActivity.Status = SaxUserActivity.DefaultStatus; - if (!internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo)) + if (!Internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo)) { - SaLogger.Warn(fn + " Failed to update User Activity for viewing: " + internals.ActivityMgr.ErrorStateMessage); - return SetErrorState(internals.ActivityMgr.ErrorStateMessage); + SaLogger.Warn(fn + " Failed to update User Activity for viewing: " + Internals.ActivityMgr.ErrorStateMessage); + return SetErrorState(Internals.ActivityMgr.ErrorStateMessage); } } else @@ -538,7 +535,7 @@ namespace Saxonite.Net.Core.Web.Base.Consoles if (!ItemId.IsValid(activityItemId)) return SetErrorState("Activity item id must be specified"); DateTime now = DateTime.Now; - SaxUserActivity userActivity = internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId); + SaxUserActivity userActivity = Internals.ActivityMgr.GetUserActivityForUserItem(userAccountId, activityItemId); if (userActivity != null) { if (userActivity.Reaction != activityReaction) @@ -553,10 +550,10 @@ namespace Saxonite.Net.Core.Web.Base.Consoles userActivity.ReactedAtFirst = now; userActivity.ReactedAtLast = now; userActivity.Status = SaxUserActivity.DefaultStatus; - if (!internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo)) + if (!Internals.ActivityMgr.UpdateUserActivity(userActivity, dbUpdateInfo)) { - SaLogger.Warn(fn + " Failed to update User Activity with reaction: " + internals.ActivityMgr.ErrorStateMessage); - return SetErrorState(internals.ActivityMgr.ErrorStateMessage); + SaLogger.Warn(fn + " Failed to update User Activity with reaction: " + Internals.ActivityMgr.ErrorStateMessage); + return SetErrorState(Internals.ActivityMgr.ErrorStateMessage); } } } diff --git a/SaxoniteCoreWeb/Base/Consoles/SaxDataCacheConsole_Web.cs b/SaxoniteCoreWeb/Base/Consoles/SaxDataCacheConsole_Web.cs index 1b41409..6d18cde 100644 --- a/SaxoniteCoreWeb/Base/Consoles/SaxDataCacheConsole_Web.cs +++ b/SaxoniteCoreWeb/Base/Consoles/SaxDataCacheConsole_Web.cs @@ -134,39 +134,46 @@ namespace Saxonite.Net.Core.Web.Base.Consoles { String fn = GetType().Name + ".RefreshRegions"; if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported."); - SaLogger.Info(fn + " START.", false); - DateTime startedAt = DateTime.Now; - // Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists. - Dictionary newRegionDataMap = new Dictionary(); - List newActiveRegionDataList = new List(); - - // Query Regions to replace cache. - SaxRegion.Params prmRegion = new SaxRegion.Params(); - prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted; - foreach (SaxRegion region in data.internals.ReferenceMgr.GetRegionList(prmRegion)) + try { - // Only interested in retaining the Data objects. - newRegionDataMap[region.Id] = region.GetData(); - region.Dispose(); - } + DateTime startedAt = DateTime.Now; - // Populate replacement list of active regions. - foreach (SaxRegion.Data regionData in newRegionDataMap.GetValues()) - { - if (IsRegionActive(newRegionDataMap, regionData)) - newActiveRegionDataList.Add(regionData.Clone()); - } + // Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists. + Dictionary newRegionDataMap = new Dictionary(); + List newActiveRegionDataList = new List(); - // Replace the list of Regions. - lock (regionsData) - { - // Assign the refreshed regions and active regions. - // NB The old regions don't need to be disposed as they are SaxRegion.Data objects. - regionsData.regionDataMap = newRegionDataMap; - regionsData.activeRegionDataList = newActiveRegionDataList; + // Query Regions to replace cache. + SaxRegion.Params prmRegion = new SaxRegion.Params(); + prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted; + foreach (SaxRegion region in data.internals.ReferenceMgr.GetRegionList(prmRegion)) + { + // Only interested in retaining the Data objects. + newRegionDataMap[region.Id] = region.GetData(); + region.Dispose(); + } + + // Populate replacement list of active regions. + foreach (SaxRegion.Data regionData in newRegionDataMap.GetValues()) + { + if (IsRegionActive(newRegionDataMap, regionData)) + newActiveRegionDataList.Add(regionData.Clone()); + } + + // Replace the list of Regions. + lock (regionsData) + { + // Assign the refreshed regions and active regions. + // NB The old regions don't need to be disposed as they are SaxRegion.Data objects. + regionsData.regionDataMap = newRegionDataMap; + regionsData.activeRegionDataList = newActiveRegionDataList; + } + SaLogger.Info(fn + " Regions found: " + regionsData.regionDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds)); + } + catch (Exception exc) + { + SaLogger.Error(fn + ExceptionUtil.GetLogMessage(exc, "refreshing Regions cache")); } - SaLogger.Info(fn + " END. Regions found: " + regionsData.regionDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds)); } private static bool IsRegionActive(Dictionary regionDataMap, SaxRegion.Data regionData) { @@ -223,39 +230,46 @@ namespace Saxonite.Net.Core.Web.Base.Consoles { String fn = GetType().Name + ".RefreshSports"; if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported."); - SaLogger.Info(fn + " START.", false); - DateTime startedAt = DateTime.Now; - // Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists. - Dictionary newSportDataMap = new Dictionary(); - List newActiveSportDataList = new List(); - - // Query Sports to replace cache. - SaxSport.Params prmSport = new SaxSport.Params(); - prmSport.StatusCat = SaxSport.StatusCat.NotDeleted; - foreach (SaxSport sport in data.internals.ReferenceMgr.GetSportList(prmSport)) + try { - // Only interested in retaining the Data objects. - newSportDataMap[sport.Id] = sport.GetData(); - sport.Dispose(); - } + DateTime startedAt = DateTime.Now; - // Populate replacement list of active sports. - foreach (SaxSport.Data sportData in newSportDataMap.GetValues()) - { - if (IsSportActive(newSportDataMap, sportData)) - newActiveSportDataList.Add(sportData.Clone()); - } + // Refreshed lists will be populated in isolation and then used to instantaneously replace the cache lists. + Dictionary newSportDataMap = new Dictionary(); + List newActiveSportDataList = new List(); - // Replace the list of Sports. - lock (sportsData) - { - // Assign the refreshed sports and active sports. - // NB The old sports don't need to be disposed as they are SaxSport.Data objects. - sportsData.sportDataMap = newSportDataMap; - sportsData.activeSportDataList = newActiveSportDataList; + // Query Sports to replace cache. + SaxSport.Params prmSport = new SaxSport.Params(); + prmSport.StatusCat = SaxSport.StatusCat.NotDeleted; + foreach (SaxSport sport in data.internals.ReferenceMgr.GetSportList(prmSport)) + { + // Only interested in retaining the Data objects. + newSportDataMap[sport.Id] = sport.GetData(); + sport.Dispose(); + } + + // Populate replacement list of active sports. + foreach (SaxSport.Data sportData in newSportDataMap.GetValues()) + { + if (IsSportActive(newSportDataMap, sportData)) + newActiveSportDataList.Add(sportData.Clone()); + } + + // Replace the list of Sports. + lock (sportsData) + { + // Assign the refreshed sports and active sports. + // NB The old sports don't need to be disposed as they are SaxSport.Data objects. + sportsData.sportDataMap = newSportDataMap; + sportsData.activeSportDataList = newActiveSportDataList; + } + SaLogger.Info(fn + " Sports found: " + sportsData.sportDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds)); + } + catch (Exception exc) + { + SaLogger.Error(fn + ExceptionUtil.GetLogMessage(exc, "refreshing Sports cache")); } - SaLogger.Info(fn + " END. Sports found: " + sportsData.sportDataMap.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds)); } private static bool IsSportActive(Dictionary sportDataMap, SaxSport.Data sportData) { diff --git a/SaxoniteCoreWeb/Base/Consoles/SaxSessionDataConsole_Web.cs b/SaxoniteCoreWeb/Base/Consoles/SaxSessionDataConsole_Web.cs new file mode 100644 index 0000000..cc32a9f --- /dev/null +++ b/SaxoniteCoreWeb/Base/Consoles/SaxSessionDataConsole_Web.cs @@ -0,0 +1,37 @@ +using Saxonite.Net.Standard.Base.App; +using Saxonite.Net.Standard.Base.Consoles; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Saxonite.Net.Core.Web.Base.Consoles +{ + //======================================================================= + public abstract class SaxSessionDataConsole_Web : SaxSessionDataConsole + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + + //------------------------------------------------------------------- + // Construction. + protected SaxSessionDataConsole_Web(SaxSessionContext sessionContext) : + base(sessionContext) + { + } + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + base.Dispose(disposing); + } + + //------------------------------------------------------------------- + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaxoniteCoreWeb/Base/Controllers/SaxController_Base.cs b/SaxoniteCoreWeb/Base/Controllers/SaxController_Base.cs index 7e29b86..56f573d 100644 --- a/SaxoniteCoreWeb/Base/Controllers/SaxController_Base.cs +++ b/SaxoniteCoreWeb/Base/Controllers/SaxController_Base.cs @@ -43,6 +43,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers // Protected construction. protected SaxController_Base(SaxLocaliser localiser) { + CreatedAt = DateTime.Now; Localiser = localiser; // Initialise console (debug) logging listener. @@ -60,6 +61,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers //------------------------------------------------------------------- // Properties. + public DateTime CreatedAt { get; } public bool DebugMode { get; } = SacsSystem.DebugMode; protected SaxApplication_Web App { get { return SaxApplication_Web.WebApp; } } public SaxAppConfig AppConfig { get { return App.AppConfig; } } @@ -102,6 +104,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers public SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } } // Consoles. public SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } } + public SaxSessionDataConsole SessionDataConsole { get { return Internals.SessionDataConsole; } } public SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } } // Managers. public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } } @@ -161,7 +164,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers //------------------------------------------------------------------- public override void OnActionExecuting(ActionExecutingContext filterContext) { - if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuting" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); + //if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuting" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); // Refreh model properties. SaxModel_Base baseModel = ViewData.Model as SaxModel_Base; @@ -171,7 +174,7 @@ namespace Saxonite.Net.Core.Web.Base.Controllers } public override void OnActionExecuted(ActionExecutedContext filterContext) { - if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuted" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); + //if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuted" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); // Refreh model properties. SaxModel_Base baseModel = ViewData.Model as SaxModel_Base; @@ -296,7 +299,17 @@ namespace Saxonite.Net.Core.Web.Base.Controllers //------------------------------------------------------------------- // Session. //------------------------------------------------------------------- - public bool ValidUserSession { get { return SessionData?.ValidUserSession ?? false; } } + public bool ValidUserSession + { + get + { + if (SessionData == null || !SessionData.ValidUserSession) return false; + SaxSessionDataConsole sessionDataConsole = SessionDataConsole; + if (sessionDataConsole != null) + sessionDataConsole.RecordUserActive(UserAccountId, CreatedAt); + return true; + } + } public SysId UserAccountId { get { return SessionData?.User?.UserAccountId ?? null; } } public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } } // Referrer. diff --git a/SaxoniteCoreWeb/Base/Models/SaxModel_Base.cs b/SaxoniteCoreWeb/Base/Models/SaxModel_Base.cs index c41294b..fc76d2a 100644 --- a/SaxoniteCoreWeb/Base/Models/SaxModel_Base.cs +++ b/SaxoniteCoreWeb/Base/Models/SaxModel_Base.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Rendering; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Core; @@ -63,6 +64,7 @@ namespace Saxonite.Net.Core.Web.Base.Models protected SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } } // Consoles. protected SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } } + protected SaxSessionDataConsole SessionDataConsole { get { return Internals.SessionDataConsole; } } protected SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } } // Managers. public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } } @@ -189,8 +191,10 @@ namespace Saxonite.Net.Core.Web.Base.Models } public bool IsPaged { get { return paging.Paged; } } public uint PageNo { get { return paging.PageNo; } set { paging.PageNo = value; } } + public String PageNoValue { get { return paging.PageNo.ToString(); } set { paging.PageNo = (uint)StringUtil.ToInt(value); } } public ushort PageLength { get { return paging.PageLength; } set { paging.PageLength = value; } } - public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / PageLength) : 0); } } + public String PageLengthValue { get { return paging.PageLength.ToString(); } set { paging.PageLength = (ushort)StringUtil.ToShort(value); } } + public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / paging.PageLength) : 0); } } public ushort ItemCount { get; set; } private ulong itemTotal = 0; public ulong ItemTotal @@ -241,6 +245,17 @@ namespace Saxonite.Net.Core.Web.Base.Models return maxLength; } + //------------------------------------------------------------------- + // Display data - Dates. + public String FormatDateForDisplay(DateTime value) + { + return DateUtil.Format(value, "d-MMM-yy", SessionCulture); + } + public String FormatDateTimeForDisplay(DateTime value) + { + return StringUtil.TrimAll(DateUtil.Format(value, "d-MMM-yy HH:mm", SessionCulture)); + } + //------------------------------------------------------------------- // Form data - Dates. // Format may depend on locale, but as long as these methods are used consistently, @@ -362,13 +377,16 @@ namespace Saxonite.Net.Core.Web.Base.Models } } - /// List of Organisation Categories for dropdown. + /// + /// List of Organisation Categories for dropdown. + /// NB Does not include 'Partner' or 'Other'. + /// public IEnumerable OrganisationCategoriesForSelect { get { List values = new List(); - foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes) + foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes.FindAll(item => item.Type != (int)Sax.OrganisationCategory.Partner && item.Type != (int)Sax.OrganisationCategory.Other)) values.Add(new SelectListItem(Localiser.GetOrganisationCategoryCaption((Sax.OrganisationCategory)organisationCategory.Type), organisationCategory.Code)); return values; } diff --git a/SaxoniteCoreWebAdmin/Admin/Consoles/SaxConsoleHelper_WebAdmin.cs b/SaxoniteCoreWebAdmin/Admin/Consoles/SaxConsoleHelper_WebAdmin.cs index d296a9c..63ee562 100644 --- a/SaxoniteCoreWebAdmin/Admin/Consoles/SaxConsoleHelper_WebAdmin.cs +++ b/SaxoniteCoreWebAdmin/Admin/Consoles/SaxConsoleHelper_WebAdmin.cs @@ -31,6 +31,14 @@ namespace Saxonite.Net.Core.Web.Admin.Consoles throw new NotSupportedException(fn + " Data Cache Console not supported in Admin app."); } + //------------------------------------------------------------------- + // Session data console. + public override SaxSessionDataConsole GetSessionDataConsole(SaxSessionContext sessionContext) + { + // Method is called by Base Controller on ValidUserSession and must return null for Admin app. + return null; + } + //------------------------------------------------------------------- // Activity console. public override SaxActivityConsole CreateActivityConsole(SaxSessionContext sessionContext) diff --git a/SaxoniteCoreWebAdmin/Admin/Controllers/SecurityController-Ajax.cs b/SaxoniteCoreWebAdmin/Admin/Controllers/SecurityController-Ajax.cs index c17e9b3..f3c261f 100644 --- a/SaxoniteCoreWebAdmin/Admin/Controllers/SecurityController-Ajax.cs +++ b/SaxoniteCoreWebAdmin/Admin/Controllers/SecurityController-Ajax.cs @@ -45,15 +45,21 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (responseData.HasControlErrors()) return Json(responseData); - // Get user account. - SaxUserAccount userAccount = GetUserAccountForSignin(model, responseData); - if (responseData.HasControlErrors()) - return Json(responseData); - - String errorMessage = ""; - if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage)) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage)); + + // Get user account. + SaxUserAccount userAccount = GetUserAccountForSignin(model, responseData); + disposableItems.Add(userAccount); + if (responseData.HasControlErrors()) + return Json(responseData); + + String errorMessage = ""; + if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage)) + { + return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage)); + } } // Construct response. @@ -74,6 +80,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers public ActionResult Ajax_KeepLoggedIn(Model_Security model) { String fn = GetType().Name + ".Ajax_KeepLoggedIn"; + if (DebugMode) SaLogger.Debug(fn); AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn(); if (!ValidUserSession) @@ -96,43 +103,50 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers private SaxUserAccount GetUserAccountForSignin(Model_Security model, AjaxResponse_Data responseData) { String fn = GetType().Name + ".GetUserAccountForSignin"; - // Identify user account. SaxUserAccount userAccount = null; - SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params(); - prmUserAccount.EmailAddress = model.EmailAddress; - prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any; - foreach (SaxUserAccount userAccountDuplicate in AccountMgr.GetUserAccountList(prmUserAccount)) - { - // Account must be active to allow sign-in. - // NB Debug testing allows 'Awaiting' accounts to sign-in. - if ((SaxSystem.DebugMode && userAccountDuplicate.Status == SaStatus.Awaiting) || userAccountDuplicate.IsStatusActive) - { - userAccount = userAccountDuplicate; - break; - } - SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SaxSystem.GetStatusDesc(userAccount.Status), true); - } - if (userAccount != null) - { - if (!SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti"))) - { - recordInvalidLoginAttempt(model, userAccount); - SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with invalid password. Attempt: " + userAccount.InvalidLoginCount, true); - userAccount = null; - } - } - if (userAccount == null && !responseData.HasControlErrors()) - responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); - // Admin - Verify that user has System Admin role. - if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin, SaxUserRole.StatusCat.ActiveOnly)) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - recordInvalidLoginAttempt(model, userAccount); - SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " without SystemAdmin role. Attempt: " + userAccount.InvalidLoginCount, true); - userAccount = null; - responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); - } + // Identify user account. + SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params(); + prmUserAccount.EmailAddress = model.EmailAddress; + prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any; + List userAccounts = AccountMgr.GetUserAccountList(prmUserAccount); + disposableItems.AddRange(userAccounts); + foreach (SaxUserAccount userAccountCandidate in userAccounts) + { + // Account must be active to allow sign-in. + // NB Debug testing allows 'Awaiting' accounts to sign-in. + if ((SaxSystem.DebugMode && userAccountCandidate.Status == SaStatus.Awaiting) || userAccountCandidate.IsStatusActive) + { + userAccount = userAccountCandidate; + break; + } + SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SaxSystem.GetStatusDesc(userAccount.Status), true); + } + if (userAccount != null) + { + // Validate password. + if (!SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti"))) + { + recordInvalidLoginAttempt(model, userAccount); + SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with invalid password. Attempt: " + userAccount.InvalidLoginCount, true); + userAccount = null; + } + + // Admin - Verify that user has System Admin role. + else if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin, SaxUserRole.StatusCat.ActiveOnly)) + { + recordInvalidLoginAttempt(model, userAccount); + SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " without SystemAdmin role. Attempt: " + userAccount.InvalidLoginCount, true); + userAccount = null; + } + } + if (userAccount == null && !responseData.HasControlErrors()) + responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]); + } return userAccount; } private void recordInvalidLoginAttempt(Model_Security model, SaxUserAccount userAccount) @@ -167,17 +181,23 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (!ValidUserSession) return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"])); - // Retrieve user account - in case sign-out needs to be recorded. - SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId); - if (userAccount == null) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - SaLogger.Warn(fn + " System error on sign-out - failed to retrieve account details. Id: " + SysId.ToInt(SessionData_Admin.User.UserAccountId)); - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"])); - } - // Clear user session. - CloseUserSession(); - SaLogger.Info(fn + " User session closed for " + userAccount.EmailAddress, true); + // Retrieve user account - in case sign-out needs to be recorded. + SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId); + disposableItems.Add(userAccount); + if (userAccount == null) + { + SaLogger.Warn(fn + " System error on sign-out - failed to retrieve account details. Id: " + SysId.ToInt(SessionData_Admin.User.UserAccountId)); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"])); + } + + // Clear user session. + CloseUserSession(); + SaLogger.Info(fn + " User session closed for " + userAccount.EmailAddress, true); + } // Clear 'Keep Logged In' cookie. ClearKeepLoggedInCookie(); @@ -227,78 +247,85 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword(model); String messageTitle = Localiser["security.caption.change.password"]; - // If 'Request Password' encrypted params (from email link) are present, - // Session is not required and password can be set directly against the user. - SaxUserAccount userAccount = null; - bool validRequestPasswordParams = false; - bool validSessionRequired = true; - RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe); - if (requestPasswordParams.IsValid()) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - // Retrieve user account details and validate email address. - userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId); - if (requestPasswordParams.IsValid(userAccount)) + + // If 'Request Password' encrypted params (from email link) are present, + // Session is not required and password can be set directly against the user. + SaxUserAccount userAccount = null; + bool validRequestPasswordParams = false; + bool validSessionRequired = true; + RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe); + if (requestPasswordParams.IsValid()) { - validRequestPasswordParams = true; - validSessionRequired = false; + // Retrieve user account details and validate email address. + userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId); + disposableItems.Add(userAccount); + if (requestPasswordParams.IsValid(userAccount)) + { + validRequestPasswordParams = true; + validSessionRequired = false; + } + else + { + SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Id: " + SysId.ToString(requestPasswordParams.UserAccountId) + ". Email: " + requestPasswordParams.EmailAddress); + userAccount = null; + } } - else + + // User must have a valid session. + if (validSessionRequired && userAccount == null && !ValidUserSession) + return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"])); + + // Retrieve user account if not previously populated. + if (userAccount == null && SessionData_Admin != null) { - SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Id: " + SysId.ToString(requestPasswordParams.UserAccountId) + ". Email: " + requestPasswordParams.EmailAddress); - userAccount = null; + userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId); + disposableItems.Add(userAccount); } - } + if (userAccount == null) + return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details.")); - // User must have a valid session. - if (validSessionRequired && userAccount == null && !ValidUserSession) - return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"])); + // Validate password data. + model.ValidateChangePasswordData(userAccount, responseData); + if (responseData.HasControlErrors()) + return Json(responseData); - // Retrieve user account if not previously populated. - if (userAccount == null && SessionData_Admin != null) - { - userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId); - } - if (userAccount == null) - return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details.")); + // Note old password. + String oldPasswordHashed = userAccount.PasswordHashed; - // Validate password data. - model.ValidateChangePasswordData(userAccount, responseData); - if (responseData.HasControlErrors()) - return Json(responseData); - - // Note old password. - String oldPasswordHashed = userAccount.PasswordHashed; - - // Store password. - userAccount.PasswordHashed = SaxPasswordHelper.GetPasswordHash(userAccount, model.Password); - userAccount.PasswordChangedAt = DateTime.Now; - // Remove new password from history if present. - for (int passwordHistoryIdx = 0; passwordHistoryIdx < userAccount.PasswordHistory.Count; passwordHistoryIdx++) - { - // Compare history password to new value. - if (SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password, userAccount.PasswordHistory[passwordHistoryIdx])) + // Store password. + userAccount.PasswordHashed = SaxPasswordHelper.GetPasswordHash(userAccount, model.Password); + userAccount.PasswordChangedAt = DateTime.Now; + // Remove new password from history if present. + for (int passwordHistoryIdx = 0; passwordHistoryIdx < userAccount.PasswordHistory.Count; passwordHistoryIdx++) { - // New password found in history - remove it. - userAccount.PasswordHistory.RemoveAt(passwordHistoryIdx); - passwordHistoryIdx--; + // Compare history password to new value. + if (SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password, userAccount.PasswordHistory[passwordHistoryIdx])) + { + // New password found in history - remove it. + userAccount.PasswordHistory.RemoveAt(passwordHistoryIdx); + passwordHistoryIdx--; + } } - } - userAccount.PasswordHistory.Remove(oldPasswordHashed); - // Add the old password at the start of the history list and validate history length. - userAccount.PasswordHistory.Add(oldPasswordHashed); - while (userAccount.PasswordHistory.Count > 10) - userAccount.PasswordHistory.RemoveAt(userAccount.PasswordHistory.Count - 1); - if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo)) - { - SaLogger.Warn(fn + " Failed to change password for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage); - return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"])); - } - SaLogger.Info(fn + " Password changed for " + userAccount.EmailAddress, true); + userAccount.PasswordHistory.Remove(oldPasswordHashed); + // Add the old password at the start of the history list and validate history length. + userAccount.PasswordHistory.Add(oldPasswordHashed); + while (userAccount.PasswordHistory.Count > 10) + userAccount.PasswordHistory.RemoveAt(userAccount.PasswordHistory.Count - 1); + if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to change password for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage); + return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"])); + } + SaLogger.Info(fn + " Password changed for " + userAccount.EmailAddress, true); - // Construct response. - responseData.Success = true; - if (validRequestPasswordParams) - responseData.SetMessage(messageTitle, Localiser["security.msg.request.password.saved"]); + // Construct response. + responseData.Success = true; + if (validRequestPasswordParams) + responseData.SetMessage(messageTitle, Localiser["security.msg.request.password.saved"]); + } return Json(responseData); } @@ -327,19 +354,25 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (responseData.HasControlErrors()) return Json(responseData); - // Retrieve user account. - SaxUserAccount userAccount = AccountMgr.GetUserAccountByEmailAddress(model.EmailAddress); - if (userAccount == null) - SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account does not exist"); - else if (!userAccount.IsStatusActive) - SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not active"); - else if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin)) - SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not 'Admin'"); - else + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - if (SendRequestPasswordEmail(this, userAccount, false)) - SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true); - else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress); + + // Retrieve user account. + SaxUserAccount userAccount = AccountMgr.GetUserAccountByEmailAddress(model.EmailAddress); + disposableItems.Add(userAccount); + if (userAccount == null) + SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account does not exist"); + else if (!userAccount.IsStatusActive) + SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not active"); + else if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin)) + SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not 'Admin'"); + else + { + if (SendRequestPasswordEmail(this, userAccount, false)) + SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true); + else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress); + } } // Construct response. diff --git a/SaxoniteCoreWebAdmin/Admin/Controllers/UserController-Ajax.cs b/SaxoniteCoreWebAdmin/Admin/Controllers/UserController-Ajax.cs index 7aa969e..8dfaf10 100644 --- a/SaxoniteCoreWebAdmin/Admin/Controllers/UserController-Ajax.cs +++ b/SaxoniteCoreWebAdmin/Admin/Controllers/UserController-Ajax.cs @@ -26,14 +26,19 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers public String Title { get; set; } public String FirstName { get; set; } public String LastName { get; set; } + public String RegionNameCountry { get; set; } + public String RegionNameProvince { get; set; } public int UserRoleCount { get; set; } public bool? SystemAdmin { get; set; } = null; + public String LastLoginAt { get; set; } } private class UserData_Full : UserData_List { + public String RegionIdCountry { get; set; } + public String RegionIdProvince { get; set; } public List userRoleCodes { get; } = new List(); } - private void PopulateUserResponseData(UserData_List data, SaxUserAccount user, List userRoles) + private void PopulateUserResponseData(SaxModel_Admin model, UserData_List data, SaxUserAccount user, List userRoles) { data.Id = SysId.ToString(user.Id); data.EmailAddress = user.EmailAddress; @@ -41,14 +46,20 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers data.Title = user.Title; data.FirstName = user.FirstName; data.LastName = user.LastName; + data.RegionNameCountry = model.GetRegionName(user.RegionIdCountry); + data.RegionNameProvince = model.GetRegionName(user.RegionIdProvince); data.UserRoleCount = userRoles?.GetUserRolesByStatus(SaStatus.Active).Count ?? 0; data.SystemAdmin = (userRoles != null && userRoles.Exists(item => item.UserRole == Sax.UserRole.SystemAdmin) ? true : (bool?)null); + data.LastLoginAt = model.FormatDateTimeForDisplay(user.LastLoginAt); data.StatusCode = user.StatusCode; data.StatusDesc = user.StatusDesc; } - private void PopulateUserResponseData(UserData_Full data, SaxUserAccount user, List userRoles) + private void PopulateUserResponseData(SaxModel_Admin model, UserData_Full data, SaxUserAccount user, List userRoles) { - PopulateUserResponseData(data as UserData_List, user, userRoles); + PopulateUserResponseData(model, data as UserData_List, user, userRoles); + + data.RegionIdCountry = SysId.ToString(user.RegionIdCountry); + data.RegionIdProvince = SysId.ToString(user.RegionIdProvince); if (userRoles != null) { @@ -122,19 +133,18 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers ColumnDfn colTitle = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Title); ColumnDfn colFirstName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.FirstName); ColumnDfn colLastName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastName); + ColumnDfn colCountryName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.RegionCountry_Name); + ColumnDfn colProvinceName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.RegionProvince_Name); ColumnDfn colUserRoleCount = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.UserRoleCount); ColumnDfn colSystemAdminUser = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.SystemAdminUser); + ColumnDfn colLastLogin = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastLoginAt); ColumnDfn colStatus = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Status); List columns = new List() { - colId, - colEmail, - colTitle, - colFirstName, - colLastName, - colUserRoleCount, - colSystemAdminUser, - colStatus + colId, colEmail, colTitle, colFirstName, colLastName, + colCountryName, colProvinceName, + colUserRoleCount, colSystemAdminUser, + colLastLogin, colStatus }; // Query items. @@ -150,8 +160,11 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers Title = values.GetString(colTitle.DataName), FirstName = values.GetString(colFirstName.DataName), LastName = values.GetString(colLastName.DataName), + RegionNameCountry = values.GetString(colCountryName.DataName), + RegionNameProvince = values.GetString(colProvinceName.DataName), UserRoleCount = StringUtil.ToInt(values.GetString(colUserRoleCount.DataName)), SystemAdmin = StringUtil.ToBool(values.GetString(colSystemAdminUser.DataName)), + LastLoginAt = model.FormatDateTimeForDisplay(values.GetDateTime(colLastLogin.DataName)), StatusCode = values.GetString(colStatus.DataName), StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName)) }; @@ -193,22 +206,28 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); RefreshModel(model); - // Identify item. - SysId userAccountId = model.UserAccountId.ToSysId(); - if (!SysId.IsValid(userAccountId)) - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"])); - - // Retrieve item. - SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId); - if (user == null) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + userAccountId + "'"); - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); - } - // Construct response. - PopulateUserResponseData(responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active)); - responseData.Success = true; + // Identify item. + SysId userAccountId = model.UserAccountId.ToSysId(); + if (!SysId.IsValid(userAccountId)) + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"])); + + // Retrieve item. + SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId); + disposableItems.Add(user); + if (user == null) + { + SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + userAccountId + "'"); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); + } + + // Construct response. + PopulateUserResponseData(model, responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active)); + responseData.Success = true; + } return Json(responseData); } @@ -229,24 +248,30 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); RefreshModel(model); - // Identify user. - if (StringUtil.IsEmpty(model.UserEmailAddress)) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - SaLogger.Warn(fn + " Attempt to get user details with blank email."); - return Json(responseData); - } - // Retrieve user. - SaxUserAccount user = AccountMgr.GetUserAccountByEmailAddress(model.UserEmailAddress); - if (user == null) - { - SaLogger.Warn(fn + " Failed to retrieve user for Email: " + model.UserEmailAddress); - return Json(responseData); - } + // Identify user. + if (StringUtil.IsEmpty(model.UserEmailAddress)) + { + SaLogger.Warn(fn + " Attempt to get user details with blank email."); + return Json(responseData); + } - // Construct response. - PopulateUserResponseData(responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active)); - responseData.Success = true; + // Retrieve user. + SaxUserAccount user = AccountMgr.GetUserAccountByEmailAddress(model.UserEmailAddress); + disposableItems.Add(user); + if (user == null) + { + SaLogger.Warn(fn + " Failed to retrieve user for Email: " + model.UserEmailAddress); + return Json(responseData); + } + + // Construct response. + PopulateUserResponseData(model, responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active)); + responseData.Success = true; + } return Json(responseData); } @@ -272,102 +297,113 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (responseData.HasControlErrors()) return Json(responseData); - // Retrieve existing item. - SysId userAccountId = model.UserAccountId.ToSysId(); - SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId); - if (user == null && SysId.IsValid(userAccountId)) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'"); - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); - } - // User roles, including deleted in case of reassignment. - List userRoles = new List(); - if (user != null) userRoles = GetUserRoles(user.Id, SaxUserRole.StatusCat.Any); - if (model.UserRoleCodes != null) - { - List userRoleCodes = model.GetUserRoleCodes(); - foreach (String userRoleCode in userRoleCodes) + // Retrieve existing item. + SysId userAccountId = model.UserAccountId.ToSysId(); + SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId); + disposableItems.Add(user); + if (user == null && SysId.IsValid(userAccountId)) { - // Role to activate. - Sax.UserRole role = (Sax.UserRole)UserRoleCodes.GetTypeForCode(userRoleCode); - SaxUserRole userRole = userRoles.GetUserRoleByRole(role); - if (userRole == null) + SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'"); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); + } + + // User roles, including deleted in case of reassignment. + List userRoles = new List(); + if (user != null) userRoles = GetUserRoles(user.Id, SaxUserRole.StatusCat.Any); + disposableItems.AddRange(userRoles); + if (model.UserRoleCodes != null) + { + List userRoleCodes = model.GetUserRoleCodes(); + foreach (String userRoleCode in userRoleCodes) { - userRole = AccountMgr.CreateUserRole(user?.Id, role); - userRoles.Add(userRole); + // Role to activate. + Sax.UserRole role = (Sax.UserRole)UserRoleCodes.GetTypeForCode(userRoleCode); + SaxUserRole userRole = userRoles.GetUserRoleByRole(role); + if (userRole == null) + { + userRole = AccountMgr.CreateUserRole(user?.Id, role); + userRoles.Add(userRole); + } + userRole.Status = SaStatus.Active; } - userRole.Status = SaStatus.Active; - } - // Roles to deactivate. - foreach (SaxUserRole userRole in userRoles) - { - if (!userRoleCodes.Exists(item => item.EqualsCI(UserRoleCodes.GetCodeForType(userRole.UserRole)))) - userRole.Status = SaStatus.Inactive; - } - } - - // Create new item if required. - if (user == null) - { - user = AccountMgr.CreateUserAccount(); - if (model.UserEmailAddress != null) - user.EmailAddress = model.UserEmailAddress; - } - - // Populate item properties. - if (model.UserTitle != null) - user.Title = model.UserTitle; - if (model.UserFirstName != null) - user.FirstName = model.UserFirstName; - if (model.UserLastName != null) - user.LastName = model.UserLastName; - if (model.UserStatusCode != null) - user.Status = model.UserStatus; - - // Store item. - if (!SysId.IsValid(user.Id)) - { - if (!AccountMgr.InsertUserAccount(user, DbUpdateInfo)) - { - SaLogger.Warn(fn + " Failed to insert user: " + AccountMgr.ErrorStateMessage); - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage)); - } - } - else - { - if (!AccountMgr.UpdateUserAccount(user, DbUpdateInfo)) - { - SaLogger.Warn(fn + " Failed to update user [Id:" + SysId.ToString(user.Id) + "]: " + AccountMgr.ErrorStateMessage); - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage)); - } - } - - if (model.UserRoleCodes != null) - { - // Associated data - User Roles. - foreach (SaxUserRole userRole in (userRoles)) - { - // Store User Role. - if (!SysId.IsValid(userRole.Id)) + // Roles to deactivate. + foreach (SaxUserRole userRole in userRoles) { - // Apply User Account Id if not yet done (for new user). - if (!SysId.IsValid(userRole.UserAccountId)) - userRole.UserAccountId = user.Id; - if (!AccountMgr.InsertUserRole(userRole, DbUpdateInfo)) - SaLogger.Warn(fn + " Failed to insert user role: " + AccountMgr.ErrorStateMessage); - } - else - { - if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo)) - SaLogger.Warn(fn + " Failed to update user role [Id:" + SysId.ToString(userRole.Id) + "]: " + AccountMgr.ErrorStateMessage); + if (!userRoleCodes.Exists(item => item.EqualsCI(UserRoleCodes.GetCodeForType(userRole.UserRole)))) + userRole.Status = SaStatus.Inactive; } } - } - // Construct response. - PopulateUserResponseData(responseData.user, user, userRoles.GetUserRolesByStatus(SaStatus.Active)); - responseData.Success = true; + // Create new item if required. + if (user == null) + { + user = AccountMgr.CreateUserAccount(); + if (model.UserEmailAddress != null) + user.EmailAddress = model.UserEmailAddress; + } + + // Populate item properties. + if (model.UserTitle != null) + user.Title = model.UserTitle; + if (model.UserFirstName != null) + user.FirstName = model.UserFirstName; + if (model.UserLastName != null) + user.LastName = model.UserLastName; + if (model.UserRegionIdCountry != null) + user.RegionIdCountry = model.UserRegionIdCountry.ToSysId(); + if (model.UserRegionIdProvince != null) + user.RegionIdProvince = model.UserRegionIdProvince.ToSysId(); + if (model.UserStatusCode != null) + user.Status = model.UserStatus; + + // Store item. + if (!SysId.IsValid(user.Id)) + { + if (!AccountMgr.InsertUserAccount(user, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to insert user: " + AccountMgr.ErrorStateMessage); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage)); + } + } + else + { + if (!AccountMgr.UpdateUserAccount(user, DbUpdateInfo)) + { + SaLogger.Warn(fn + " Failed to update user [Id:" + SysId.ToString(user.Id) + "]: " + AccountMgr.ErrorStateMessage); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage)); + } + } + + if (model.UserRoleCodes != null) + { + // Associated data - User Roles. + foreach (SaxUserRole userRole in (userRoles)) + { + // Store User Role. + if (!SysId.IsValid(userRole.Id)) + { + // Apply User Account Id if not yet done (for new user). + if (!SysId.IsValid(userRole.UserAccountId)) + userRole.UserAccountId = user.Id; + if (!AccountMgr.InsertUserRole(userRole, DbUpdateInfo)) + SaLogger.Warn(fn + " Failed to insert user role: " + AccountMgr.ErrorStateMessage); + } + else + { + if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo)) + SaLogger.Warn(fn + " Failed to update user role [Id:" + SysId.ToString(userRole.Id) + "]: " + AccountMgr.ErrorStateMessage); + } + } + } + + // Construct response. + PopulateUserResponseData(model, responseData.user, user, userRoles.GetUserRolesByStatus(SaStatus.Active)); + responseData.Success = true; + } return Json(responseData); } @@ -387,53 +423,60 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); RefreshModel(model); - // Retrieve user. - SysId userAccountId = model.UserAccountId.ToSysId(); - SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId); - if (userAccount == null) + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'"); - return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); - } - // User roles. - List userRoles = GetUserRoles(userAccount.Id, SaxUserRole.StatusCat.Any); - bool systemAdmin = userRoles.Exists(item => item.UserRole == Sax.UserRole.SystemAdmin); + // Retrieve user. + SysId userAccountId = model.UserAccountId.ToSysId(); + SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId); + disposableItems.Add(userAccount); + if (userAccount == null) + { + SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'"); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); + } - // Email types to send. - bool sendAcountActivationOnline = StringUtil.ToBool(model.UserEmailAcountActivationOnline); - bool sendRequestPasswordOnline = StringUtil.ToBool(model.UserEmailRequestPasswordOnline); - bool sendRequestPasswordAdmin = StringUtil.ToBool(model.UserEmailRequestPasswordAdmin); - if (!sendAcountActivationOnline && !sendRequestPasswordOnline && !sendRequestPasswordAdmin) - return Json(responseData.SetMessage(messageTitle, Localiser["msg.validation.select.option"])); - if (sendRequestPasswordAdmin && !systemAdmin) - return Json(responseData.SetMessage(messageTitle, Localiser["user.msg.not.system.admin"])); + // User roles. + List userRoles = GetUserRoles(userAccount.Id, SaxUserRole.StatusCat.Any); + disposableItems.AddRange(userRoles); + bool systemAdmin = userRoles.Exists(item => item.UserRole == Sax.UserRole.SystemAdmin); - // Send emails. - int emailsSentCount = 0; - if (sendAcountActivationOnline) - { - if (!OrganisationController.SendAccountActivatedEmail(this, userAccount)) - return Json(responseData.SetMessage(messageTitle, "Failed to send Online Accoutn Activation email.")); - emailsSentCount++; - } - if (sendRequestPasswordOnline) - { - if (!SecurityController.SendRequestPasswordEmail(this, userAccount, true)) - return Json(responseData.SetMessage(messageTitle, "Failed to send Online Request Password email.")); - emailsSentCount++; - } - if (sendRequestPasswordAdmin) - { - if (!SecurityController.SendRequestPasswordEmail(this, userAccount, false)) - return Json(responseData.SetMessage(messageTitle, "Failed to send Admin Request Password email.")); - emailsSentCount++; - } + // Email types to send. + bool sendAcountActivationOnline = StringUtil.ToBool(model.UserEmailAcountActivationOnline); + bool sendRequestPasswordOnline = StringUtil.ToBool(model.UserEmailRequestPasswordOnline); + bool sendRequestPasswordAdmin = StringUtil.ToBool(model.UserEmailRequestPasswordAdmin); + if (!sendAcountActivationOnline && !sendRequestPasswordOnline && !sendRequestPasswordAdmin) + return Json(responseData.SetMessage(messageTitle, Localiser["msg.validation.select.option"])); + if (sendRequestPasswordAdmin && !systemAdmin) + return Json(responseData.SetMessage(messageTitle, Localiser["user.msg.not.system.admin"])); - // Construct response. - PopulateUserResponseData(responseData.user, userAccount, userRoles.GetUserRolesByStatus(SaStatus.Active)); - responseData.SetNotification(messageTitle, "Emails sent: " + emailsSentCount); - responseData.Success = true; + // Send emails. + int emailsSentCount = 0; + if (sendAcountActivationOnline) + { + if (!OrganisationController.SendAccountActivatedEmail(this, userAccount)) + return Json(responseData.SetMessage(messageTitle, "Failed to send Online Accoutn Activation email.")); + emailsSentCount++; + } + if (sendRequestPasswordOnline) + { + if (!SecurityController.SendRequestPasswordEmail(this, userAccount, true)) + return Json(responseData.SetMessage(messageTitle, "Failed to send Online Request Password email.")); + emailsSentCount++; + } + if (sendRequestPasswordAdmin) + { + if (!SecurityController.SendRequestPasswordEmail(this, userAccount, false)) + return Json(responseData.SetMessage(messageTitle, "Failed to send Admin Request Password email.")); + emailsSentCount++; + } + + // Construct response. + PopulateUserResponseData(model, responseData.user, userAccount, userRoles.GetUserRolesByStatus(SaStatus.Active)); + responseData.SetNotification(messageTitle, "Emails sent: " + emailsSentCount); + responseData.Success = true; + } return Json(responseData); } diff --git a/SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController-Ajax.cs b/SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController-Ajax.cs new file mode 100644 index 0000000..6ee45e1 --- /dev/null +++ b/SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController-Ajax.cs @@ -0,0 +1,190 @@ +using Microsoft.AspNetCore.Mvc; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.Utils; +using Saxonite.Net.Core.Web.Admin.Models; +using Saxonite.Net.Standard.Base.App; +using Saxonite.Net.Standard.Base.Bus; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Saxonite.Net.Core.Web.Admin.Controllers +{ + //======================================================================= + partial class UsersOnlineController + { + //------------------------------------------------------------------- + // User data. + private class UserData_List : AjaxResponse_Data.ItemData_Base + { + public String EmailAddress { get; set; } + public String DisplayName { get; set; } + public String Title { get; set; } + public String FirstName { get; set; } + public String LastName { get; set; } + public String LastLoginAt { get; set; } + public String OnlineAt { get; set; } + } + private class UserData_Full : UserData_List + { + } + private void PopulateUserResponseData(SaxModel_Admin model, UserData_List data, SaxUserAccount user, SaxUserOnline userOnline) + { + data.Id = SysId.ToString(user.Id); + data.EmailAddress = user.EmailAddress; + data.DisplayName = user.GetDisplayName(); + data.Title = user.Title; + data.FirstName = user.FirstName; + data.LastName = user.LastName; + data.LastLoginAt = model.FormatDateTimeForDisplay(user.LastLoginAt); + if (userOnline != null) + data.OnlineAt = model.FormatDateTimeForDisplay(userOnline.OnlineAt); + } + private void PopulateUserResponseData(SaxModel_Admin model, UserData_Full data, SaxUserAccount user, SaxUserOnline userOnline) + { + PopulateUserResponseData(model, data as UserData_List, user, userOnline); + } + + //------------------------------------------------------------------- + // Utilities. + + //------------------------------------------------------------------- + // Get User List ajax request. + //------------------------------------------------------------------- + private class AjaxResponse_UserList : AjaxResponse_Data + { + // Construction. + public AjaxResponse_UserList(SaxModel_Admin model) : base(model) { } + // User list. + public List Users { get; } = new List(); + } + //------------------------------------------------------------------- + [HttpPost] + [Route("UsersOnline/Ajax/GetList")] + [Route("UsersOnline/Ajax/GetUsers")] + [Route("UsersOnline/Ajax/GetUserList")] + public IActionResult Ajax_GetUsersOnlineList(Model_User model) + { + String fn = GetType().Name + ".Ajax_GetUsersOnlineList"; + String messageTitle = Localiser["label.users.online"]; + AjaxResponse_UserList responseData = new AjaxResponse_UserList(model); + + // Validate session and referrer. + if (!ValidReferer) return Forbid(); + if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); + RefreshModel(model); + + // Item filter. + SaxUserOnline.Params prmUserOnline = new SaxUserOnline.Params(); + prmUserOnline.UserFilterPartial = model.Filter.User; + prmUserOnline.SportIdsIncluded.AddId(model.Filter.UserSportId.ToSysId()); + prmUserOnline.RegionIdsIncluded.AddId(model.Filter.UserRegionId.ToSysId()); + model.Paging.ItemTotal = (ulong)AccountMgr.GetUserOnlineCount(prmUserOnline); + model.PopulatePagingTo(prmUserOnline.ResultsPaging); + + // Item columns. + ColumnDfn colUserAccountId = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.UserAccountId); + ColumnDfn colEmail = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.EmailAddress); + ColumnDfn colTitle = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.Title); + ColumnDfn colFirstName = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.FirstName); + ColumnDfn colLastName = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.LastName); + ColumnDfn colLastLogin = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.LastLoginAt); + ColumnDfn colOnlineAt = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.OnlineAt); + List columns = new List() + { + colUserAccountId, + colEmail, colTitle, colFirstName, colLastName, + colLastLogin, colOnlineAt + }; + + // Query items. + using (DbResultSet resultSet = AccountMgr.ExecuteUserOnlineQuery(columns, prmUserOnline)) + { + while (resultSet != null && resultSet.HasRows && resultSet.NextRecord()) + { + NamedValue.Map values = resultSet.GetRecordValues(); + UserData_List userData = new UserData_List() + { + Id = values.GetString(colUserAccountId.DataName), + EmailAddress = values.GetString(colEmail.DataName), + Title = values.GetString(colTitle.DataName), + FirstName = values.GetString(colFirstName.DataName), + LastName = values.GetString(colLastName.DataName), + LastLoginAt = model.FormatDateTimeForDisplay(values.GetDateTime(colLastLogin.DataName)), + OnlineAt = model.FormatDateTimeForDisplay(values.GetDateTime(colOnlineAt.DataName)), + }; + userData.DisplayName = SaxUserAccount.GetDisplayName(userData.Title, userData.FirstName, userData.LastName); + responseData.Users.Add(userData); + } + } + if (model.Paging.IsPaged) + model.Paging.ItemCount = (ushort)responseData.Users.Count; + + // Complete response. + responseData.SetPaging(model); + responseData.Success = true; + return Json(responseData); + } + + //------------------------------------------------------------------- + // Get User ajax request. + //------------------------------------------------------------------- + private class AjaxResponse_User : AjaxResponse_Data + { + // Construction. + public AjaxResponse_User(SaxModel_Admin model) : base(model) { } + // User details. + public UserData_Full user { get; } = new UserData_Full(); + } + //------------------------------------------------------------------- + [HttpPost] + [Route("UsersOnline/Ajax/GetItem")] + [Route("UsersOnline/Ajax/GetUser")] + public IActionResult Ajax_GetUser(Model_User model) + { + String fn = GetType().Name + ".Ajax_GetUser"; + String messageTitle = Localiser["user.caption.user"]; + AjaxResponse_User responseData = new AjaxResponse_User(model); + + // Validate session and referrer. + if (!ValidReferer) return Forbid(); + if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); + RefreshModel(model); + + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) + { + + // Identify item. + SysId userAccountId = model.UserAccountId.ToSysId(); + if (!SysId.IsValid(userAccountId)) + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"])); + + // Retrieve item. + SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId); + disposableItems.Add(user); + if (user == null) + { + SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + userAccountId + "'"); + return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"])); + } + + // User Online. + SaxUserOnline userOnline = AccountMgr.GetUserOnlineByUserAccountId(user.Id); + disposableItems.Add(userOnline); + + // Construct response. + PopulateUserResponseData(model, responseData.user, user, userOnline); + responseData.Success = true; + } + return Json(responseData); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController.cs b/SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController.cs new file mode 100644 index 0000000..a204ff1 --- /dev/null +++ b/SaxoniteCoreWebAdmin/Admin/Controllers/UsersOnlineController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Sarand.Net.Standard.Base.Utils; +using Saxonite.Net.Core.Web.Admin.Models; +using Saxonite.Net.Core.Web.Base.App; +using Saxonite.Net.Core.Web.Base.Controllers; +using Saxonite.Net.Standard.Base.Bus; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace Saxonite.Net.Core.Web.Admin.Controllers +{ + //======================================================================= + public partial class UsersOnlineController : SaxController_Admin + { + //------------------------------------------------------------------- + // Construction. + public UsersOnlineController(SaxLocaliser localiser, ILogger logger) : + base(localiser, logger) + { + } + + //------------------------------------------------------------------- + [HttpGet] + public IActionResult Index(String sta) + { + // Validate session. + if (!ValidUserSession) + return RedirectToAction("", ""); + + // Paging. + Model_UsersOnline model = new Model_UsersOnline(Localiser); + model.Paging.PageNo = 1; + model.Paging.PageLength = 24; + + return View(model); + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaxoniteCoreWebAdmin/Admin/Models/Model_User.cs b/SaxoniteCoreWebAdmin/Admin/Models/Model_User.cs index e545b5d..48946be 100644 --- a/SaxoniteCoreWebAdmin/Admin/Models/Model_User.cs +++ b/SaxoniteCoreWebAdmin/Admin/Models/Model_User.cs @@ -67,6 +67,8 @@ namespace Saxonite.Net.Core.Web.Admin.Models public String UserTitle { get; set; } public String UserFirstName { get; set; } public String UserLastName { get; set; } + public String UserRegionIdCountry { get; set; } + public String UserRegionIdProvince { get; set; } public String UserRoleCodeDelim = ","; public new List UserRoleCodes { get; set; } public List GetUserRoleCodes() @@ -92,63 +94,126 @@ namespace Saxonite.Net.Core.Web.Admin.Models // User. public bool ValidateUserData(SaxController_Base.AjaxResponse_Data responseData) { - // User email. - if (UserEmailAddress != null) + String fn = GetType().Name + ".ValidateUserData"; + + // Record disposable items as they are created so all can be disposed when needed. + using (DisposableCollection disposableItems = new DisposableCollection()) { - UserEmailAddress = UserEmailAddress.ToLowerInvariant(); - responseData.SetControlValue(nameof(UserEmailAddress), UserEmailAddress); - if (StringUtil.IsEmpty(UserEmailAddress)) - responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.mandatory.value"]); - else if (StringUtil.GetLength(UserEmailAddress) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)) - responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)]); - else if (!SacsDataValidationHelper.IsValidEmailAddress(UserEmailAddress)) - responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.invalid.value"]); - else + + // User email. + if (UserEmailAddress != null) { - // Check for duplicate email address. - SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params(); - SysId userAccountId = UserAccountId.ToSysId(); - if (SysId.IsValid(userAccountId)) - prmUserAccount.IdsExcluded.Add(userAccountId); - prmUserAccount.EmailAddress = UserEmailAddress; - prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any; - if (AccountMgr.GetUserAccountCount(prmUserAccount) > 0) - responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.duplicate.value"]); + UserEmailAddress = UserEmailAddress.ToLowerInvariant(); + responseData.SetControlValue(nameof(UserEmailAddress), UserEmailAddress); + if (StringUtil.IsEmpty(UserEmailAddress)) + responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.mandatory.value"]); + else if (StringUtil.GetLength(UserEmailAddress) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)) + responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)]); + else if (!SacsDataValidationHelper.IsValidEmailAddress(UserEmailAddress)) + responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.invalid.value"]); + else + { + // Check for duplicate email address. + SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params(); + SysId userAccountId = UserAccountId.ToSysId(); + if (SysId.IsValid(userAccountId)) + prmUserAccount.IdsExcluded.Add(userAccountId); + prmUserAccount.EmailAddress = UserEmailAddress; + prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any; + if (AccountMgr.GetUserAccountCount(prmUserAccount) > 0) + responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.duplicate.value"]); + } } - } - // User title. - if (UserTitle != null) - { - } + // User title. + if (UserTitle != null) + { + } - // User first name. - if (UserFirstName != null) - { - responseData.SetControlValue(nameof(UserFirstName), UserFirstName); - if (StringUtil.IsEmpty(UserFirstName)) - responseData.SetControlError(nameof(UserFirstName), Localiser["msg.validation.mandatory.value"]); - else if (StringUtil.GetLength(UserFirstName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)) - responseData.SetControlError(nameof(UserFirstName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)]); - } + // User first name. + if (UserFirstName != null) + { + responseData.SetControlValue(nameof(UserFirstName), UserFirstName); + if (StringUtil.IsEmpty(UserFirstName)) + responseData.SetControlError(nameof(UserFirstName), Localiser["msg.validation.mandatory.value"]); + else if (StringUtil.GetLength(UserFirstName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)) + responseData.SetControlError(nameof(UserFirstName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)]); + } - // User last name. - if (UserLastName != null) - { - responseData.SetControlValue(nameof(UserLastName), UserLastName); - if (StringUtil.IsEmpty(UserLastName)) - responseData.SetControlError(nameof(UserLastName), Localiser["msg.validation.mandatory.value"]); - else if (StringUtil.GetLength(UserLastName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)) - responseData.SetControlError(nameof(UserLastName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)]); - } + // User last name. + if (UserLastName != null) + { + responseData.SetControlValue(nameof(UserLastName), UserLastName); + if (StringUtil.IsEmpty(UserLastName)) + responseData.SetControlError(nameof(UserLastName), Localiser["msg.validation.mandatory.value"]); + else if (StringUtil.GetLength(UserLastName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)) + responseData.SetControlError(nameof(UserLastName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)]); + } + + // Country region. + SaxRegion regionCountry = null; + if (UserRegionIdCountry != null) + { + if (!SysId.IsValid(UserRegionIdCountry.ToSysId())) + responseData.SetControlError(nameof(UserRegionIdCountry), Localiser["msg.validation.mandatory.value"]); + else + { + regionCountry = ReferenceMgr.GetRegionById(UserRegionIdCountry.ToSysId()); + disposableItems.Add(regionCountry); + if (regionCountry != null) + { + if (regionCountry.RegionType != Sax.RegionType.Country) + { + SaLogger.Warn(fn + " Invalid region type. Expected: Country. Found: " + regionCountry.RegionType.ToString()); + responseData.SetControlError(nameof(UserRegionIdCountry), Localiser["msg.validation.invalid.value"]); + } + } + else + { + SaLogger.Warn(fn + " Failed to retrieve region for Id: " + UserRegionIdCountry); + responseData.SetControlError(nameof(UserRegionIdCountry), Localiser["msg.validation.invalid.value"]); + } + } + } + + // Province region. + SaxRegion regionProvince = null; + if (UserRegionIdProvince != null) + { + if (!UserRegionIdProvince.IsEmpty()) + { + regionProvince = ReferenceMgr.GetRegionById(UserRegionIdProvince.ToSysId()); + disposableItems.Add(regionProvince); + if (regionProvince != null) + { + if (regionProvince.RegionType != Sax.RegionType.Province) + { + SaLogger.Warn(fn + " Invalid region type. Expected: Province . Found: " + regionCountry.RegionType.ToString()); + responseData.SetControlError(nameof(UserRegionIdProvince), Localiser["msg.validation.invalid.value"]); + } + else if (regionCountry != null && !regionProvince.RegionIdParent.Equals(regionCountry.Id)) + { + SaLogger.Warn(fn + " Invalid province region parent. Expected: " + regionCountry.Id.ToString() + " . Found: " + regionCountry.RegionIdParent.ToString()); + responseData.SetControlError(nameof(UserRegionIdProvince), Localiser["msg.validation.invalid.value"]); + } + } + else + { + SaLogger.Warn(fn + " Failed to retrieve region for Id: " + UserRegionIdProvince); + responseData.SetControlError(nameof(UserRegionIdProvince), Localiser["msg.validation.invalid.value"]); + } + } + } + + // User status. + if (UserStatusCode != null) + { + if (StringUtil.IsEmpty(UserStatusCode)) + responseData.SetControlError(nameof(UserStatus), Localiser["msg.validation.mandatory.value"]); + else if (!SaxUserAccount.ValidStatuses.Contains(UserStatus)) + responseData.SetControlError(nameof(UserStatus), Localiser["msg.validation.invalid.value"]); + } - // User status. - if (UserStatusCode != null) - { - if (StringUtil.IsEmpty(UserStatusCode)) - responseData.SetControlError(nameof(UserStatus), Localiser["msg.validation.mandatory.value"]); - else if (!SaxUserAccount.ValidStatuses.Contains(UserStatus)) - responseData.SetControlError(nameof(UserStatus), Localiser["msg.validation.invalid.value"]); } return !responseData.HasControlErrors(); diff --git a/SaxoniteCoreWebAdmin/Admin/Models/Model_UsersOnline.cs b/SaxoniteCoreWebAdmin/Admin/Models/Model_UsersOnline.cs new file mode 100644 index 0000000..a0aa640 --- /dev/null +++ b/SaxoniteCoreWebAdmin/Admin/Models/Model_UsersOnline.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Mvc.Rendering; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Helpers; +using Sarand.Net.Standard.Base.Utils; +using Saxonite.Base.App; +using Saxonite.Net.Core.Web.Base.App; +using Saxonite.Net.Core.Web.Base.Controllers; +using Saxonite.Net.Core.Web.Base.Models; +using Saxonite.Net.Standard.Base.App; +using Saxonite.Net.Standard.Base.Bus; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace Saxonite.Net.Core.Web.Admin.Models +{ + //======================================================================= + public class Model_UsersOnline : SaxModel_Admin + { + //------------------------------------------------------------------- + // Construction. + public Model_UsersOnline(SaxLocaliser localiser) : + base(localiser) + { + AdditionalFiles_JS.Add("js/sax-usr-onl.js"); + } + + //------------------------------------------------------------------- + // User filter properties. + public class FilterData + { + public String User { get; set; } + public String UserSportId { get; set; } + public String UserRegionId { get; set; } + } + public FilterData Filter { get; } = new FilterData(); + + //------------------------------------------------------------------- + // User properties. + public String UserId { get; set; } + [EmailAddress] + public String UserEmailAddress { get; set; } + public String UserTitle { get; set; } + public String UserFirstName { get; set; } + public String UserLastName { get; set; } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaxoniteCoreWebAdmin/Admin/Models/SaxModel_Admin.cs b/SaxoniteCoreWebAdmin/Admin/Models/SaxModel_Admin.cs index 2d04aeb..07dbdc1 100644 --- a/SaxoniteCoreWebAdmin/Admin/Models/SaxModel_Admin.cs +++ b/SaxoniteCoreWebAdmin/Admin/Models/SaxModel_Admin.cs @@ -2,6 +2,7 @@ using Saxonite.Net.Core.Web.Admin.App; using Saxonite.Net.Core.Web.Base.App; using Saxonite.Net.Core.Web.Base.Models; +using Saxonite.Net.Standard.Base.Bus; using System; using System.Collections.Generic; using System.Linq; @@ -26,6 +27,27 @@ namespace Saxonite.Net.Core.Web.Admin.Models public SaxSessionData_Admin SessionData_Admin { get { return SessionData as SaxSessionData_Admin; } } public SysId UserAccountId { get { return SessionData_Admin?.User?.UserAccountId ?? null; } } + //------------------------------------------------------------------- + // Reference data. + //------------------------------------------------------------------- + public String GetSportName(SysId sportId) + { + using (SaxSport sport = ReferenceMgr.GetSportById(sportId)) + { + if (sport != null) return sport.Name; + } + return ""; + } + + public String GetRegionName(SysId regionId) + { + using (SaxRegion region = ReferenceMgr.GetRegionById(regionId)) + { + if (region != null) return region.Name; + } + return ""; + } + //------------------------------------------------------------------- } //======================================================================= diff --git a/SaxoniteCoreWebAdmin/Localisation/SaxAdminProperties.resx b/SaxoniteCoreWebAdmin/Localisation/SaxAdminProperties.resx index 1bf6e98..75045a0 100644 --- a/SaxoniteCoreWebAdmin/Localisation/SaxAdminProperties.resx +++ b/SaxoniteCoreWebAdmin/Localisation/SaxAdminProperties.resx @@ -330,6 +330,12 @@ Select + + Select Country + + + Select Province + {0} selected @@ -369,6 +375,9 @@ User roles + + Users online + Yes diff --git a/SaxoniteCoreWebAdmin/Views/Organisation/Panel-OrganisationDialog.cshtml b/SaxoniteCoreWebAdmin/Views/Organisation/Panel-OrganisationDialog.cshtml index a2800a6..963cb7a 100644 --- a/SaxoniteCoreWebAdmin/Views/Organisation/Panel-OrganisationDialog.cshtml +++ b/SaxoniteCoreWebAdmin/Views/Organisation/Panel-OrganisationDialog.cshtml @@ -7,7 +7,7 @@ @using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationId", @class = "sax-form" })) @@ -92,7 +92,7 @@ - @* Organisation region *@ + @* Organisation country *@
diff --git a/SaxoniteCoreWebAdmin/Views/Shared/_Layout.cshtml b/SaxoniteCoreWebAdmin/Views/Shared/_Layout.cshtml index 4b8c994..2bb02dc 100644 --- a/SaxoniteCoreWebAdmin/Views/Shared/_Layout.cshtml +++ b/SaxoniteCoreWebAdmin/Views/Shared/_Layout.cshtml @@ -24,9 +24,9 @@ @if (Model.ProhibitCache) { - - - + + + } @@ -50,12 +50,12 @@ @foreach (var cssFile in Model.AdditionalFiles_CSS) { - + } @* Scripts *@ @@ -109,52 +109,53 @@ *@ @if (Model.AwaitingLogin) { - - + + } else { - - - - + + + + }
@@ -164,9 +165,9 @@ @* Page caption *@ @if (!StringUtil.IsEmpty(Model.PageCaption)) { -
-
@(Model.PageCaption)
-
+
+
@(Model.PageCaption)
+
} @@ -220,11 +221,11 @@ @foreach (var jsFile in Model.AdditionalFiles_JS) { - + } @if (Model.IncludeGoogleAnalytics) { - + } @RenderSection("Scripts", required: false) diff --git a/SaxoniteCoreWebAdmin/Views/User/Index.cshtml b/SaxoniteCoreWebAdmin/Views/User/Index.cshtml index 6e8e3d8..ba143b0 100644 --- a/SaxoniteCoreWebAdmin/Views/User/Index.cshtml +++ b/SaxoniteCoreWebAdmin/Views/User/Index.cshtml @@ -77,7 +77,7 @@
- @* Organisation Status *@ + @* User Status *@
@Localiser["label.status"]
@@ -134,12 +134,12 @@
-
Roles:
-
+
Last login:
-
+
Roles:
+
diff --git a/SaxoniteCoreWebAdmin/Views/User/Panel-UserDialog.cshtml b/SaxoniteCoreWebAdmin/Views/User/Panel-UserDialog.cshtml index 45c205b..6080820 100644 --- a/SaxoniteCoreWebAdmin/Views/User/Panel-UserDialog.cshtml +++ b/SaxoniteCoreWebAdmin/Views/User/Panel-UserDialog.cshtml @@ -7,6 +7,8 @@ + +
+ +
+ +
+ + @* Filter Panel *@ + @using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserFilter", @class = "sax-form", @onSubmit = "return false;" })) + { + +
+ @* Header - Page caption, paging controls, actions. *@ +
+
@Localiser["label.users.online"]
+
+
+ +
+
+ @* Burger menu *@ + +
+
+
+
+ @* User filter - email, name etc. *@ +
+
@Localiser["user.caption.user"]
+
@Html.TextBoxFor(model => model.Filter.User, new { @id = "ifFilterUser", @class = "FilterUser", @maxlength = "32" })
+
+ @* User sport *@ +
+
@Localiser["label.sport"]
+
+
+ @Html.HiddenFor(model => model.Filter.UserSportId, new { @id = "", @class = "FilterUserSportId" }) +
+
+
@(Localiser[
+
@(Localiser[
+
+
+
+
+ @* User region *@ +
+
@Localiser["label.region"]
+
+
+ @Html.HiddenFor(model => model.Filter.UserRegionId, new { @id = "", @class = "FilterUserRegionId" }) +
+
+
@(Localiser[
+
@(Localiser[
+
+
+
+
+
+ + @* Filter element templates *@ +
+ @* Burger dropdown item template - ul is ignored *@ +
    +
  • XXX
  • +
+
+ +
+ } + +
+ + @* Separate filter from results when alondside each other. *@ +
+ +
+ +
+ + @* Results Panel *@ +
+
+
+ + @* Element templates *@ +
+ + @* User item template *@ +
+
+
+ @* User email *@ +
+
+
+ @* Burger menu *@ + +
+
+
+
+
+
Login:
+
Online:
+
+
+
+ + @* Burger dropdown item template - ul is ignored *@ +
    +
  • XXX
  • +
+ +
+
+
+
+
+ diff --git a/SaxoniteCoreWebAdmin/wwwroot/js/sax-main.js b/SaxoniteCoreWebAdmin/wwwroot/js/sax-main.js index d88c8c3..e47f127 100644 --- a/SaxoniteCoreWebAdmin/wwwroot/js/sax-main.js +++ b/SaxoniteCoreWebAdmin/wwwroot/js/sax-main.js @@ -21,6 +21,10 @@ { return !isNaN(parseFloat(n)) && isFinite(n); } + sax.getTime = function () + { + return (new Date()).getTime(); + } sax.zeroPad = function (num, count) { var val = num + ''; @@ -353,37 +357,35 @@ //-------------------------------------------------------------- // Session. - sax.configureKeepLoggedIn = function () + sax.configureKeepLoggedIn = function (single) { sax.debug('KeepLoggedIn Configuring'); - setTimeout(sax.doKeepLoggedIn, Math.max(30000, sax.keepLoggedInDelay || 300000)); + let kli = sax.kli; + if (!kli.timeout) kli.timeout = setTimeout(function () { sax.doKeepLoggedIn(single); }, Math.max(30000, kli.interval || 300000)); } - sax.doKeepLoggedIn = function () + sax.doKeepLoggedIn = function (single) { - //sax.debug('KeepLoggedIn Calling'); + sax.debug('KeepLoggedIn Calling'); + let kli = sax.kli; + delete kli.timeout; var onKeepLoggedIn = function (response) { sax.debug('KeepLoggedIn Responded', response); - sax.configureKeepLoggedIn(); + kli.lastAt = sax.getTime(); + if (!single) sax.configureKeepLoggedIn(); } sax.doAjaxRequest({ url: sax.url.base + 'Security/Ajax/KeepLoggedIn', formId: 'frmNavigate', noWaitIcon: true, callbackFn: onKeepLoggedIn }, 1000) } sax.configureSessionRefresh = function () { - //sax.debug('SessionRefresh Configuring'); + sax.debug('SessionRefresh Configuring'); $(document).on('mousedown mousemove touchstart scroll keydown', sax.triggeRefreshSession); } sax.triggeRefreshSession = function () { - //sax.session = sax.session || { refresh: {} }; - let ssr = sax.session.refresh; - win.clearTimeout(ssr.timeout); - ssr.timeout = setTimeout(function () { sax.refreshSession(); }, 150000); - } - sax.refreshSession = function () - { - //sax.debug('sax.refreshSession'); - sax.doKeepLoggedIn(); + if (sax.kli.timeout) return; + sax.debug('SessionRefresh Triggering'); + sax.configureKeepLoggedIn(1); } //-------------------------------------------------------------- @@ -499,7 +501,7 @@ $(function () { $('.sax-button-cta,.sax-button-action,.sax-action-link,.sax-social-icon.sax-action').prop('tabindex', 0); - if (sax.keepLoggedIn) + if (sax.kli.active) sax.configureKeepLoggedIn(); else if (!sax.awaitingLogin) sax.configureSessionRefresh(); diff --git a/SaxoniteCoreWebAdmin/wwwroot/js/sax-user.js b/SaxoniteCoreWebAdmin/wwwroot/js/sax-user.js index 973713f..8e5d42d 100644 --- a/SaxoniteCoreWebAdmin/wwwroot/js/sax-user.js +++ b/SaxoniteCoreWebAdmin/wwwroot/js/sax-user.js @@ -61,7 +61,12 @@ $('.UserTitle', $item).text(item.title); $('.UserFirstName', $item).text(item.firstName); $('.UserLastName', $item).text(item.lastName); + $('.UserRegionNameCountry', $item).text(item.regionNameCountry); + $('.UserRegionNameProvince', $item).text(item.regionNameProvince); $('.UserRoleCount', $item).text(item.userRoleCount); + $('.UserRoles', $item).toggle(!!item.userRoleCount); + $('.UserLastLoginAt', $item).text(item.lastLoginAt); + $('.UserLastLogin', $item).toggle(!!item.lastLoginAt); $('.StatusDesc', $item).text(item.statusDesc).addClass('sax-status-' + item.statusCode.toLowerCase()); $('.sax-caption .sax-action').toggleClass('sax-action', !item.deleted); let $mnu = $('ul.sax-dropdown-menu', $item); @@ -104,6 +109,7 @@ } //-------------------------------------------------------------- + // Sports. sax.selectFilterSport = function () { if (!sax.data.sports) @@ -111,8 +117,10 @@ let handleFilterSportsResponse = function (response) { if (response && response.success && response.sports) + { sax.data.sports = response.sports; - sax.showSelectFilterSport(); + sax.showSelectFilterSport(); + } } sax.ajax.filterSports = sax.ajax.filterSports || { url: sax.url.base + 'Sport/Ajax/GetList', formId: 'frmNavigate', callbackFn: handleFilterSportsResponse }; sax.doAjaxRequest(sax.ajax.filterSports, 50); @@ -125,33 +133,41 @@ } sax.onFilterSportSelected = function (spt) { - //sax.debug('onFilterSportSelected', sax.$frm.filter) - let $frm = sax.$frm.filter; - $('.FilterUserSportId', $frm).val(spt.id); - $('.FilterUserSportCaption', $frm).val(spt.name); + //sax.debug('onFilterSportSelected', $frm.filter) + let $frmFlt = $frm.filter; + $('.FilterUserSportId', $frmFlt).val(spt.id); + $('.FilterUserSportCaption', $frmFlt).val(spt.name); sax.refreshResults(); } sax.clearFilterSport = function () { - $('.FilterUserSportId,.FilterUserSportCaption', sax.$frm.filter).val(''); + $('.FilterUserSportId,.FilterUserSportCaption', $frm.filter).val(''); sax.refreshResults(); } //-------------------------------------------------------------- - sax.selectFilterRegion = function () + // Regions. + sax.populateRegions = function (fnCallback) { if (!sax.data.regions) { - let handleFilterRegionsResponse = function (response) + let handleRegionsResponse = function (response) { if (response && response.success && response.regions) + { sax.data.regions = response.regions; - sax.showSelectFilterRegion(); + if (fnCallback) fnCallback(); + } } - sax.ajax.regions = sax.ajax.regions || { url: sax.url.base + 'Region/Ajax/GetList', formId: 'frmNavigate', callbackFn: handleFilterRegionsResponse }; + sax.ajax.regions = sax.ajax.regions || { url: sax.url.base + 'Region/Ajax/GetList', formId: 'frmNavigate', callbackFn: handleRegionsResponse }; sax.doAjaxRequest(sax.ajax.regions, 50); } - else sax.showSelectFilterRegion(); + else if (fnCallback) fnCallback(); + } + // Filter region. + sax.selectFilterRegion = function () + { + sax.populateRegions(sax.showSelectFilterRegion); } sax.showSelectFilterRegion = function () { @@ -160,14 +176,58 @@ sax.onFilterRegionSelected = function (rgn) { //sax.debug('onFilterRegionSelected', rgn); - let $frm = sax.$frm.filter; - $('.FilterUserRegionId', $frm).val(rgn.id); - $('.FilterUserRegionCaption', $frm).val(rgn.name); + let $frmFlt = $frm.filter; + $('.FilterUserRegionId', $frmFlt).val(rgn.id); + $('.FilterUserRegionCaption', $frmFlt).val(rgn.name); sax.refreshResults(); } sax.clearFilterRegion = function () { - $('.FilterUserRegionId,.FilterUserRegionCaption', sax.$frm.filter).val(''); + $('.FilterUserRegionId,.FilterUserRegionCaption', $frm.filter).val(''); + sax.refreshResults(); + } + // Country region. + sax.selectRegionCountry = function () + { + sax.populateRegions(sax.showSelectRegionCountry); + } + sax.showSelectRegionCountry = function () + { + sax.data.selectItem.fn.selectItem({ caption: sax.dlg.title.selectRegionCountry, items: sax.data.regions, fnCallback: sax.onRegionCountrySelected }); + } + sax.onRegionCountrySelected = function (rgn) + { + //sax.debug('onRegionCountrySelected', rgn); + let $frmUsr = $frm.user; + $('.UserRegionIdCountry', $frmUsr).val(rgn.id); + $('.UserRegionCaptionCountry', $frmUsr).val(rgn.name); + sax.refreshResults(); + } + sax.clearRegionCountry = function () + { + $('.UserRegionIdCountry,.UserRegionCaptionCountry', $frm.user).val(''); + sax.refreshResults(); + } + // Province region. + sax.selectRegionProvince = function () + { + sax.populateRegions(sax.showSelectRegionProvince); + } + sax.showSelectRegionProvince = function () + { + sax.data.selectItem.fn.selectItem({ caption: sax.dlg.title.selectRegionProvince, items: sax.data.regions, fnCallback: sax.onRegionProvinceSelected }); + } + sax.onRegionProvinceSelected = function (rgn) + { + //sax.debug('onRegionProvinceSelected', rgn); + let $frmUsr = $frm.user; + $('.UserRegionIdProvince', $frmUsr).val(rgn.id); + $('.UserRegionCaptionProvince', $frmUsr).val(rgn.name); + sax.refreshResults(); + } + sax.clearRegionProvince = function () + { + $('.UserRegionIdProvince,.UserRegionCaptionProvince', $frm.user).val(''); sax.refreshResults(); } @@ -203,14 +263,18 @@ //-------------------------------------------------------------- sax.createItem = function () + { + sax.clearUserData(); + $dlg.user.dialog('option', 'title', sax.dlg.title.createUser); + sax.openDialog($dlg.user, 600, 480); + } + sax.clearUserData = function () { let frm = $frm.user; - $('.UserAccountId,.UserRoleCodes,input[type="text"],textarea,select', frm).val(''); + $('.UserAccountId,.UserRegionIdCountry,.UserRegionIdProvince,.UserRoleCodes,input[type="text"],textarea,select', frm).val(''); $('.UserStatus', frm).val(sax.data.defaultStatusCode); sax.clearControlErrors(); sax.userAccount = { userRoleCodes: [] }; - $dlg.user.dialog('option', 'title', sax.dlg.title.createUser); - sax.openDialog($dlg.user, 600, 480); } //-------------------------------------------------------------- @@ -223,16 +287,19 @@ sax.onUpdateItem = function (response) { //sax.debug('onUpdateItem', response); - let frm = $frm.user; - $('.UserAccountId,input[type="text"],textarea,select', frm).val(''); - sax.clearControlErrors(); if (!response.success) return; + sax.clearUserData(); let item = sax.userAccount = response.user; + let frm = $frm.user; $('.UserAccountId', frm).val(item.id); $('.UserEmailAddress', frm).val(item.emailAddress); $('.UserTitle', frm).val(item.title); $('.UserFirstName', frm).val(item.firstName); $('.UserLastName', frm).val(item.lastName); + $('.UserRegionIdCountry', frm).val(item.regionIdCountry); + $('.UserRegionCaptionCountry', frm).val(item.regionNameCountry); + $('.UserRegionIdProvince', frm).val(item.regionIdProvince); + $('.UserRegionCaptionProvince', frm).val(item.regionNameProvince); $('.UserRoles', frm).val(sax.getSelectedUserRoleDescs().join('\n')); $('#ifUserRoleCodes').val(item.userRoleCodes.join(sax.data.userRoleCodeDelim)); $('.UserStatus', frm).val(item.statusCode); diff --git a/SaxoniteCoreWebAdmin/wwwroot/js/sax-usr-onl.js b/SaxoniteCoreWebAdmin/wwwroot/js/sax-usr-onl.js new file mode 100644 index 0000000..06292f7 --- /dev/null +++ b/SaxoniteCoreWebAdmin/wwwroot/js/sax-usr-onl.js @@ -0,0 +1,316 @@ +/* + * Admin - Users Online page scripts. + */ + +(function (win, doc, undef) +{ + "use strict"; + let $pnl = sax.$pnl, $frm = sax.$frm, $dlg = sax.$dlg; + sax.itemIdPrefix = 'Itm_'; + sax.userAccount = {}; + + //-------------------------------------------------------------- + sax.refreshResults = function (delay) + { + if (!sax.awaitingRefreshResponse) + { + $('.sax-burger', $pnl.filter).hide(); + var $items = $('.sax-panel-item-list', $pnl.results).fadeOut(200).empty(); + var $msgs = $('.sax-panel-messages', $pnl.results).hide().empty().append($('
' + sax.msg.dataQuerying + '
')).fadeIn(200); + } + sax.ajax.refreshResults = sax.ajax.refreshResults || { url: sax.url.base + 'UsersOnline/Ajax/GetList', formId: 'frmUserFilter', callbackFn: sax.doRefreshResults }; + sax.doAjaxRequest(sax.ajax.refreshResults, delay || 50); + sax.awaitingRefreshResponse = true; + } + sax.doRefreshResults = function (response) + { + //sax.debug('doRefreshResults', response); + delete sax.awaitingRefreshResponse; + sax.refreshControlValues(response.controlValues); + var $msgs = $('.sax-panel-messages', $pnl.results).hide().empty(); + var $items = $('.sax-panel-item-list', $pnl.results).hide().empty(); + sax.refreshPaging(response, '#frmUserFilter', sax.refreshResults); + if (!response.success) return; + // Reference data - Roles. + if (response.userRoles && response.userRoles.length) + sax.data.userRoles = response.userRoles; + // Reference data - Statuses. + if (response.defaultStatusCode) sax.data.defaultStatusCode = response.defaultStatusCode; + if (response.statuses) + sax.populateSelectCodeDesc('#cbUserStatus', response.statuses); + // Menu. + var $bgr = $('.sax-burger', $pnl.filter).hide(); + var $mnu = $('ul.sax-dropdown-menu', $bgr).empty(); + $mnu.append(sax.getFilterMenuOption(sax.lbl.refresh, function () { sax.refreshResults(); })); + $bgr.toggle(!!$('.sax-dropdown-item', $mnu).length); + // Items. + var items = response.users; + if (!items.length) + return $msgs.append($('
' + sax.msg.dataNoneFound + '
')).fadeIn(500); + for (let itemIdx = 0; itemIdx < items.length; itemIdx++) + $items.append(sax.populateItem(sax.getItemTemplate(), items[itemIdx])); + $items.fadeIn(50); + } + sax.populateItem = function ($item, item) + { + if (!item) return; + $item.prop('id', sax.itemIdPrefix + item.id); + $('.UserEmailAddress', $item).text(item.emailAddress); + $('.UserDisplayName', $item).text(item.displayName); + $('.UserLastLoginAt', $item).text(item.lastLoginAt); + $('.UserLastOnlineAt', $item).text(item.onlineAt); + $('.sax-caption .sax-action').toggleClass('sax-action', !item.deleted); + let $mnu = $('ul.sax-dropdown-menu', $item); + if (!item.deleted) + { + //$mnu.append(sax.getItemMenuOption(sax.lbl.edit, function () { sax.updateItem(item) })); + //$('.sax-header .sax-action.sax-item-update', $item).off('click').on('click', function () { sax.updateItem(item) }); + //$item.off('dblclick').on('dblclick', function () { sax.updateItem(item) }); + } + $('.sax-burger', $item).toggle(!!$('.sax-dropdown-item', $mnu).length); + $('.sax-panel-messages', $pnl.results).hide().empty(); + return $item; + } + + //-------------------------------------------------------------- + sax.getFilterDropdownTemplate = function () + { + return $('.sax-panel-item-template .sax-dropdown-menu.UserFilter li.sax-dropdown-item', $pnl.filter).clone(); + } + sax.getItemTemplate = function () + { + return $('.sax-panel-item-template .sax-panel-item.User', $pnl.results).clone(); + } + sax.getItemDropdownTemplate = function () + { + return $('.sax-panel-item-template .sax-dropdown-menu.User li.sax-dropdown-item', $pnl.results).clone(); + } + sax.getFilterMenuOption = function (lbl, fn) + { + return sax.getMenuOption(sax.getFilterDropdownTemplate(), lbl, fn); + } + sax.getItemMenuOption = function (lbl, fn) + { + return sax.getMenuOption(sax.getItemDropdownTemplate(), lbl, fn); + } + sax.getMenuOption = function ($opt, lbl, fn) + { + return $opt.find('div.sax-label').text(lbl).end().on('click', fn); + } + + //-------------------------------------------------------------- + sax.selectFilterSport = function () + { + if (!sax.data.sports) + { + let handleFilterSportsResponse = function (response) + { + if (response && response.success && response.sports) + sax.data.sports = response.sports; + sax.showSelectFilterSport(); + } + sax.ajax.filterSports = sax.ajax.filterSports || { url: sax.url.base + 'Sport/Ajax/GetList', formId: 'frmNavigate', callbackFn: handleFilterSportsResponse }; + sax.doAjaxRequest(sax.ajax.filterSports, 50); + } + else sax.showSelectFilterSport(); + } + sax.showSelectFilterSport = function () + { + sax.data.selectItem.fn.selectItem({ caption: sax.dlg.title.selectSport, items: sax.data.sports, fnCallback: sax.onFilterSportSelected }); + } + sax.onFilterSportSelected = function (spt) + { + //sax.debug('onFilterSportSelected', sax.$frm.filter) + let $frm = sax.$frm.filter; + $('.FilterUserSportId', $frm).val(spt.id); + $('.FilterUserSportCaption', $frm).val(spt.name); + sax.refreshResults(); + } + sax.clearFilterSport = function () + { + $('.FilterUserSportId,.FilterUserSportCaption', sax.$frm.filter).val(''); + sax.refreshResults(); + } + + //-------------------------------------------------------------- + sax.selectFilterRegion = function () + { + if (!sax.data.regions) + { + let handleFilterRegionsResponse = function (response) + { + if (response && response.success && response.regions) + sax.data.regions = response.regions; + sax.showSelectFilterRegion(); + } + sax.ajax.regions = sax.ajax.regions || { url: sax.url.base + 'Region/Ajax/GetList', formId: 'frmNavigate', callbackFn: handleFilterRegionsResponse }; + sax.doAjaxRequest(sax.ajax.regions, 50); + } + else sax.showSelectFilterRegion(); + } + sax.showSelectFilterRegion = function () + { + sax.data.selectItem.fn.selectItem({ caption: sax.dlg.title.selectFilterRegion, items: sax.data.regions, fnCallback: sax.onFilterRegionSelected }); + } + sax.onFilterRegionSelected = function (rgn) + { + //sax.debug('onFilterRegionSelected', rgn); + let $frm = sax.$frm.filter; + $('.FilterUserRegionId', $frm).val(rgn.id); + $('.FilterUserRegionCaption', $frm).val(rgn.name); + sax.refreshResults(); + } + sax.clearFilterRegion = function () + { + $('.FilterUserRegionId,.FilterUserRegionCaption', sax.$frm.filter).val(''); + sax.refreshResults(); + } + + //-------------------------------------------------------------- + // User Roles. + sax.selectUserRoles = function () + { + //sax.debug('selectUserRoles'); + sax.selectUserRoleConfig = sax.selectUserRoleConfig || { caption: sax.dlg.title.selectUserRoles, width: 520, fnCallback: sax.updateUserRoles }; + sax.selectUserRoleConfig.codes = sax.data.userRoles; + sax.selectUserRoleConfig.selectedCodes = sax.userAccount.userRoleCodes || []; + sax.selectCode.fn.openSelectCode(sax.selectUserRoleConfig); + } + sax.updateUserRoles = function (userRoleCodes) + { + //sax.debug('updateUserRoles', userRoleCodes); + sax.userAccount.userRoleCodes = userRoleCodes; + $('#ifUserRoleCodes').val(userRoleCodes.join(sax.data.userRoleCodeDelim)); + $('#ifUserRoles').val(sax.getSelectedUserRoleDescs().join('\n')); + } + sax.getSelectedUserRoleDescs = function () + { + let userRoles = []; + sax.userAccount.userRoleCodes.forEach(function (value) { userRoles.push(sax.getUserRoleDesc(value)); }) + return userRoles.sortCI(); + } + sax.getUserRoleDesc = function (roleCode) + { + let roleDesc = ''; + sax.data.userRoles.some(function (value) { if (value.code == roleCode) { roleDesc = value.desc; return true; } return false; }) + return roleDesc || roleCode; + } + + //-------------------------------------------------------------- + sax.createItem = function () + { + let frm = $frm.user; + $('.UserAccountId,.UserRoleCodes,input[type="text"],textarea,select', frm).val(''); + $('.UserStatus', frm).val(sax.data.defaultStatusCode); + sax.clearControlErrors(); + sax.userAccount = { userRoleCodes: [] }; + $dlg.user.dialog('option', 'title', sax.dlg.title.createUser); + sax.openDialog($dlg.user, 600, 480); + } + + //-------------------------------------------------------------- + sax.updateItem = function (item) + { + $('.UserAccountId', $frm.userId).val(item.id); + sax.ajax.updateItem = sax.ajax.updateItem || { url: sax.url.base + 'User/Ajax/GetItem', formId: 'frmUserId', callbackFn: sax.onUpdateItem }; + sax.doAjaxRequest(sax.ajax.updateItem, 50); + } + sax.onUpdateItem = function (response) + { + //sax.debug('onUpdateItem', response); + let frm = $frm.user; + $('.UserAccountId,input[type="text"],textarea,select', frm).val(''); + sax.clearControlErrors(); + if (!response.success) return; + let item = sax.userAccount = response.user; + $('.UserAccountId', frm).val(item.id); + $('.UserEmailAddress', frm).val(item.emailAddress); + $('.UserTitle', frm).val(item.title); + $('.UserFirstName', frm).val(item.firstName); + $('.UserLastName', frm).val(item.lastName); + $('.UserRoles', frm).val(sax.getSelectedUserRoleDescs().join('\n')); + $('#ifUserRoleCodes').val(item.userRoleCodes.join(sax.data.userRoleCodeDelim)); + $('.UserStatus', frm).val(item.statusCode); + $dlg.user.dialog('option', 'title', sax.dlg.title.updateUser); + sax.openDialog($dlg.user, 600, 480); + } + + //-------------------------------------------------------------- + sax.storeItem = function () + { + sax.ajax.storeItem = sax.ajax.storeItem || { url: sax.url.base + 'User/Ajax/StoreItem', formId: 'frmUser', callbackFn: sax.onStoreItem }; + sax.doAjaxRequest(sax.ajax.storeItem, 50); + } + sax.onStoreItem = function (response) + { + //sax.debug('onStoreItem', response); + sax.refreshControlValues(response.controlValues); + if (!response.success) return; + $dlg.user.dialog('close'); + + var item = response.user; + var $item = $('#' + sax.itemIdPrefix + item.id); + if (!$item.length) + { + $item = sax.getItemTemplate(); + $('.sax-panel-item-list', $pnl.results).prepend($item).show(); + } + $item.replaceWith(sax.populateItem(sax.getItemTemplate(), item)); + } + + //-------------------------------------------------------------- + sax.userEmails = function (item) + { + sax.debug('userEmails', item); + let frm = $frm.emailsl + $('.UserAccountId', frm).val(item.id); + $('.UserEmailAddress', frm).val(item.emailAddress); + $('.UserDisplayName', frm).val(item.displayName); + $('.RequestPasswordAdminOption', frm).toggle(item.systemAdmin); + $('input[type="checkbox"]', frm).prop('checked', false); + $dlg.emails.dialog('option', { 'title': sax.dlg.title.selectUserEmails }); + sax.openDialog($dlg.emails, 600, 360); + } + sax.sendUserEmails = function () + { + sax.ajax.sendEmails = sax.ajax.sendEmails || { url: sax.url.base + 'User/Ajax/SendEmails', formId: 'frmUserEmails', callbackFn: sax.onUserEmails }; + sax.doAjaxRequest(sax.ajax.sendEmails, 50); + } + sax.onUserEmails = function (response) + { + if (!response || !response.success) return; + $dlg.emails.dialog('close'); + } + + //-------------------------------------------------------------- + $(function () + { + $pnl.users = $('.sax-panel.Users'); + $pnl.filter = $('.sax-panel-filter', $pnl.users); + $pnl.results = $('.sax-panel-results', $pnl.users); + + $frm.filter = $('#frmUserFilter'); + $('.FilterUser', $frm.filter).on('input', function () { sax.refreshResults(250); }); + $('.FilterUserStatusCategory', $frm.filter).on('change', function () { sax.refreshResults(50); }); + $frm.userId = $('#frmUserId'); + $frm.user = $('#frmUser'); + $('.sax-input, .sax-textarea', $frm.user).on('input', function () { sax.clearControlError(this); }); + $('.sax-select', $frm.user).on('change', function () { sax.clearControlError(this); }); + $frm.emails = $('#frmUserEmails'); + + var opt = sax.dlgOpts(); + opt.buttons[sax.dlg.btn.ok] = function () { sax.storeItem(); }; + opt.buttons[sax.dlg.btn.cancel] = function () { $(this).dialog('close'); }; + $dlg.user = $('#dlgUser').dialog(opt); + + opt = sax.dlgOpts(); + opt.buttons[sax.dlg.btn.ok] = function () { sax.sendUserEmails(); }; + opt.buttons[sax.dlg.btn.cancel] = function () { $(this).dialog('close'); }; + $dlg.emails = $('#dlgUserEmails').dialog(opt); + + setTimeout(sax.refreshResults, 250); + }); + +}(window, window.document)); + +/* The End */ \ No newline at end of file diff --git a/SaxoniteCoreWebOnline/Online/Consoles/SaxConsoleHelper_WebOnline.cs b/SaxoniteCoreWebOnline/Online/Consoles/SaxConsoleHelper_WebOnline.cs index 488e8e2..e88ab22 100644 --- a/SaxoniteCoreWebOnline/Online/Consoles/SaxConsoleHelper_WebOnline.cs +++ b/SaxoniteCoreWebOnline/Online/Consoles/SaxConsoleHelper_WebOnline.cs @@ -37,6 +37,20 @@ namespace Saxonite.Net.Core.Web.Online.Consoles return dataCacheConsole; } + //------------------------------------------------------------------- + // Session Data console. + private readonly Dictionary sessionDataConsoles = new Dictionary(); + public override SaxSessionDataConsole GetSessionDataConsole(SaxSessionContext sessionContext) + { + if (sessionContext == null) throw new NotSupportedException(GetType().Name + ".GetSessionDataConsole Session Context must be specified"); + SaxSessionDataConsole_Online sessionDataConsole = sessionDataConsoles.GetValueOrDefault(sessionContext.Index, null); + if (sessionDataConsole == null) + { + sessionDataConsole = sessionDataConsoles[sessionContext.Index] = new SaxSessionDataConsole_Online(sessionContext); + } + return sessionDataConsole; + } + //------------------------------------------------------------------- // Activity console. public override SaxActivityConsole CreateActivityConsole(SaxSessionContext sessionContext) diff --git a/SaxoniteCoreWebOnline/Online/Consoles/SaxDataCacheConsole_Online.cs b/SaxoniteCoreWebOnline/Online/Consoles/SaxDataCacheConsole_Online.cs index c13f398..f66187c 100644 --- a/SaxoniteCoreWebOnline/Online/Consoles/SaxDataCacheConsole_Online.cs +++ b/SaxoniteCoreWebOnline/Online/Consoles/SaxDataCacheConsole_Online.cs @@ -15,6 +15,7 @@ namespace Saxonite.Net.Core.Web.Online.Consoles private System.Timers.Timer refreshTimer; //------------------------------------------------------------------- + // Construction. public SaxDataCacheConsole_Online(SaxSessionContext sessionContext) : base(sessionContext) { diff --git a/SaxoniteCoreWebOnline/Online/Consoles/SaxSessionDataConsole_Online.cs b/SaxoniteCoreWebOnline/Online/Consoles/SaxSessionDataConsole_Online.cs new file mode 100644 index 0000000..7dd9965 --- /dev/null +++ b/SaxoniteCoreWebOnline/Online/Consoles/SaxSessionDataConsole_Online.cs @@ -0,0 +1,119 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Bus; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Utils; +using Saxonite.Net.Core.Web.Base.Consoles; +using Saxonite.Net.Standard.Base.App; +using Saxonite.Net.Standard.Base.Bus; +using Saxonite.Net.Standard.Base.Consoles; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Saxonite.Net.Core.Web.Online.Consoles +{ + //======================================================================= + public class SaxSessionDataConsole_Online : SaxSessionDataConsole_Web + { + //------------------------------------------------------------------- + // Definitions. + + //------------------------------------------------------------------- + // Data members. + private static SaxSessionDataConsole_Online console = null; + private System.Timers.Timer commitTimer = null; + /// Latest time that a user was active on the online system - awaiting commit to the database. + private SacsQueueDictionary usersActiveAt = new SacsQueueDictionary(); + private DateTime lastDataCommit = DateTime.MinValue; + private int commitBatchSize = 32; + + //------------------------------------------------------------------- + // Construction. + public SaxSessionDataConsole_Online(SaxSessionContext sessionContext) : + base(sessionContext) + { + String fn = GetType().Name + ".CTR"; + if (console != null) + { + SaLogger.Info(fn + " Instance already exists"); + throw new InvalidOperationException(GetType().Name + " - instance already exists."); + } + console = this; + SaLogger.Info(fn + ""); + + // Initilise commit timer. + commitTimer = new System.Timers.Timer(Math.Max(2, Sax.Config.Online.DataCache.RefreshIntervalSecs.Initial) * 1000); + commitTimer.Elapsed += CommitTimer_Elapsed; + commitTimer.Start(); + } + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (Object.ReferenceEquals(console, this)) + console = null; + } + base.Dispose(disposing); + } + + //------------------------------------------------------------------- + // Properties. + + //------------------------------------------------------------------- + // Utilities. + + //------------------------------------------------------------------- + // Record User Online activity. + //------------------------------------------------------------------- + public override bool RecordUserActive(SysId userAccountId, DateTime activeAt) + { + if (!SysId.IsValid(userAccountId) || !DateUtil.IsValid(activeAt)) return false; + usersActiveAt.SetEntry(userAccountId, new SaxUserOnline.DataProperties() { UserAccountId = userAccountId, OnlineAt = activeAt }); + return true; + } + + //------------------------------------------------------------------- + // Commit User Online activity. + //------------------------------------------------------------------- + public override DateTime LastDataCommit() + { + lock (usersActiveAt) + return lastDataCommit; + } + private void CommitTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + commitTimer.Stop(); + CommitData(); + } + private void CommitData() + { + String fn = GetType().Name + ".CommitData"; + commitTimer.Stop(); + + // Extract data to be committed. + List> usersActiveAtToCommit = usersActiveAt.RemoveTopEntries(commitBatchSize); + + // Reset the timer. + int intervalSecs = Sax.Config.Online.Session.DataCommit.IntervalSecs.NoDataWaiting; + if (usersActiveAt.Count > 0) + { + // Additional Data is waiting to be committed. + intervalSecs = Sax.Config.Online.Session.DataCommit.IntervalSecs.DataWaiting; + } + commitTimer.Interval = Math.Max(1, intervalSecs) * 1000; + commitTimer.Start(); + + // Commit the data. + if (usersActiveAtToCommit.Count > 0) + { + if (SacsSystem.DebugMode) + SaLogger.Info(fn + " Entries to commit: " + usersActiveAtToCommit.Count); + Internals.AccountMgr.CommitUsersOnline(usersActiveAtToCommit.Select(ItemId => ItemId.Value).ToList()); + } + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Organisations.cs b/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Organisations.cs index 609326f..f714404 100644 --- a/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Organisations.cs +++ b/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Organisations.cs @@ -48,9 +48,9 @@ namespace Saxonite.Net.Core.Web.Online.Controllers [HttpPost] [Route("Search/Ajax/Organisations")] [Route("Search/Ajax/GetOrganisations")] - public IActionResult GetProfileOrganisations(Model_Search_Organisations model) + public IActionResult Ajax_GetOrganisations(Model_Search_Organisations model) { - String fn = GetType().Name + ".GetProfileOrganisations"; + String fn = GetType().Name + ".Ajax_GetOrganisations"; AjaxResponse_Search_Organisations responseData = new AjaxResponse_Search_Organisations(model); String messageTitle = Localiser["organisation.caption.organisations"]; @@ -68,6 +68,8 @@ namespace Saxonite.Net.Core.Web.Online.Controllers prmOrganisation.OrganisationFilter = model.Filter.Organisation; prmOrganisation.OrganisationType = model.FilterOrganisationType; prmOrganisation.OrganisationCategory = model.FilterOrganisationCategory; + prmOrganisation.OrganisationCategoriesExcluded.Add(Sax.OrganisationCategory.Partner); + prmOrganisation.OrganisationCategoriesExcluded.Add(Sax.OrganisationCategory.Other); if (SysId.IsValid(model.FilterSportId)) prmOrganisation.SportIdsIncluded.Add(model.FilterSportId); if (SysId.IsValid(model.FilterRegionId)) @@ -151,9 +153,9 @@ namespace Saxonite.Net.Core.Web.Online.Controllers [HttpPost] [Route("Search/Ajax/Organisation")] [Route("Search/Ajax/GetOrganisation")] - public IActionResult GetProfileOrganisation(Model_Search_Organisations model) + public IActionResult Ajax_GetOrganisation(Model_Search_Organisations model) { - String fn = GetType().Name + ".GetProfileOrganisation"; + String fn = GetType().Name + ".Ajax_GetOrganisation"; AjaxResponse_Search_Organisation responseData = new AjaxResponse_Search_Organisation(model); String messageTitle = Localiser["label.organisation"]; diff --git a/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Users.cs b/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Users.cs index 3d84a4b..32ffb2b 100644 --- a/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Users.cs +++ b/SaxoniteCoreWebOnline/Online/Controllers/SearchController-Ajax-Users.cs @@ -43,9 +43,9 @@ namespace Saxonite.Net.Core.Web.Online.Controllers [HttpPost] [Route("Search/Ajax/Users")] [Route("Search/Ajax/GetUsers")] - public IActionResult GetProfileUsers(Model_Search_Users model) + public IActionResult Ajax_GetUsers(Model_Search_Users model) { - String fn = GetType().Name + ".GetProfileUsers"; + String fn = GetType().Name + ".Ajax_GetUsers"; AjaxResponse_Search_Users responseData = new AjaxResponse_Search_Users(model); String messageTitle = Localiser["label.users"]; @@ -54,18 +54,19 @@ namespace Saxonite.Net.Core.Web.Online.Controllers if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData); RefreshModel(model); + // Ensure data is paged to avoid leaking large volumes. + model.Paging.PageNo = Math.Max(1, model.Paging.PageNo); + model.Paging.PageLength = Math.Max(Math.Min(Sax.Config.Online.Search.Users.PageLengthMax, model.Paging.PageLength), (ushort)1); + // Ensure filter has something to restrict the list. // ToDo May be able to show some results, such as friends when no filter provided. if (StringUtil.IsEmpty(StringUtil.TrimAll(model.Filter.User))) { + responseData.SetPaging(model); responseData.Success = true; return Json(responseData); } - // Ensure data is paged to avoid leaking large volumes. - model.Paging.PageNo = Math.Max(1, model.Paging.PageNo); - model.Paging.PageLength = Math.Max(Math.Min(Sax.Config.Online.Search.Users.PageLengthMax, model.Paging.PageLength), (ushort)1); - // Query users that user is a member of. SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params(); prmUserAccount.UserFilter = model.Filter.User; @@ -142,9 +143,9 @@ namespace Saxonite.Net.Core.Web.Online.Controllers [HttpPost] [Route("Search/Ajax/User")] [Route("Search/Ajax/GetUser")] - public IActionResult GetProfileUser(Model_Search_Users model) + public IActionResult Ajax_GetUser(Model_Search_Users model) { - String fn = GetType().Name + ".GetProfileUser"; + String fn = GetType().Name + ".Ajax_GetUser"; AjaxResponse_Search_User responseData = new AjaxResponse_Search_User(model); String messageTitle = Localiser["label.user"]; diff --git a/SaxoniteCoreWebOnline/Online/Controllers/SecurityController-Ajax.cs b/SaxoniteCoreWebOnline/Online/Controllers/SecurityController-Ajax.cs index b32bfb2..9f95ff3 100644 --- a/SaxoniteCoreWebOnline/Online/Controllers/SecurityController-Ajax.cs +++ b/SaxoniteCoreWebOnline/Online/Controllers/SecurityController-Ajax.cs @@ -77,6 +77,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers public ActionResult Ajax_KeepLoggedIn(Model_Security model) { String fn = GetType().Name + ".Ajax_KeepLoggedIn"; + if (DebugMode) SaLogger.Debug(fn); AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn(); // Validate session and referrer. diff --git a/SaxoniteCoreWebOnline/Online/Models/Model_Article.cs b/SaxoniteCoreWebOnline/Online/Models/Model_Article.cs index e50ab2d..e599b82 100644 --- a/SaxoniteCoreWebOnline/Online/Models/Model_Article.cs +++ b/SaxoniteCoreWebOnline/Online/Models/Model_Article.cs @@ -6,6 +6,7 @@ using Sarand.Net.Standard.Base.Utils; using Saxonite.Base.App; using Saxonite.Net.Core.Web.Base.App; using Saxonite.Net.Core.Web.Base.Controllers; +using Saxonite.Net.Standard.Base.App; using Saxonite.Net.Standard.Base.Bus; using System; using System.Collections.Generic; @@ -130,7 +131,7 @@ namespace Saxonite.Net.Core.Web.Online.Models responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]); else if (!DateUtil.IsValid(publishedOn)) responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]); - else if (publishedOn.CompareTo(DateTime.Today) > 0) + else if (publishedOn.CompareTo(DateUtil.IncrementDate(DateTime.Today, Sax.Config.Online.Articles.PublishMaxDaysAhead)) > 0) responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]); } diff --git a/SaxoniteCoreWebOnline/SaxoniteCoreWebOnline.csproj b/SaxoniteCoreWebOnline/SaxoniteCoreWebOnline.csproj index 2e00d7e..a85a9eb 100644 --- a/SaxoniteCoreWebOnline/SaxoniteCoreWebOnline.csproj +++ b/SaxoniteCoreWebOnline/SaxoniteCoreWebOnline.csproj @@ -11,6 +11,7 @@ + diff --git a/SaxoniteCoreWebOnline/Views/Article/Index.cshtml b/SaxoniteCoreWebOnline/Views/Article/Index.cshtml index c0de6fd..166bac3 100644 --- a/SaxoniteCoreWebOnline/Views/Article/Index.cshtml +++ b/SaxoniteCoreWebOnline/Views/Article/Index.cshtml @@ -7,6 +7,7 @@
diff --git a/SaxoniteCoreWebOnline/Views/Article/Panel-ArticleDialog.cshtml b/SaxoniteCoreWebOnline/Views/Article/Panel-ArticleDialog.cshtml index 719266d..558f57b 100644 --- a/SaxoniteCoreWebOnline/Views/Article/Panel-ArticleDialog.cshtml +++ b/SaxoniteCoreWebOnline/Views/Article/Panel-ArticleDialog.cshtml @@ -122,7 +122,7 @@
@Html.TextBoxFor(model => model.ArticlePublishOn, new { @id = "ifArticlePublishOn", @class = "sax-input sax-calendar ArticlePublishOn", @readonly = "readonly" })
diff --git a/SaxoniteCoreWebOnline/Views/Common/OrganisationControls.cshtml b/SaxoniteCoreWebOnline/Views/Common/OrganisationControls.cshtml index db34736..59d00a7 100644 --- a/SaxoniteCoreWebOnline/Views/Common/OrganisationControls.cshtml +++ b/SaxoniteCoreWebOnline/Views/Common/OrganisationControls.cshtml @@ -35,7 +35,7 @@
- @Html.DropDownList("", Model.OrganisationCategoriesForSelect, Localiser["label.select"], new { @id = "", @name = "Organisation.Category", @class = "OrganisationCategory sax-select" }) + @Html.DropDownList("", Model.OrganisationCategoriesForSelect, Localiser["label.select"] + ":", new { @id = "", @name = "Organisation.Category", @class = "OrganisationCategory sax-select" })
@@ -48,7 +48,7 @@
- @Html.DropDownList("", Model.OrganisationTypesForSelect, Localiser["label.select"], new { @id = "", @name = "Organisation.Type", @class = "OrganisationType sax-select" }) + @Html.DropDownList("", Model.OrganisationTypesForSelect, Localiser["label.select"] + ":", new { @id = "", @name = "Organisation.Type", @class = "OrganisationType sax-select" })
diff --git a/SaxoniteCoreWebOnline/Views/Common/ResultsPaging.cshtml b/SaxoniteCoreWebOnline/Views/Common/ResultsPaging.cshtml index 108c306..c577022 100644 --- a/SaxoniteCoreWebOnline/Views/Common/ResultsPaging.cshtml +++ b/SaxoniteCoreWebOnline/Views/Common/ResultsPaging.cshtml @@ -9,8 +9,8 @@ @* Results paging *@