Add project files.

This commit is contained in:
2021-05-11 19:38:43 +01:00
parent d316c1898d
commit ae64e14da4
1401 changed files with 283421 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31019.35
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteCoreWebAdmin", "SaxoniteCoreWebAdmin\SaxoniteCoreWebAdmin.csproj", "{F646283D-F7C2-42DD-AB1E-E741455664BD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteStandardDbas", "SaxoniteStandardDbas\SaxoniteStandardDbas.csproj", "{DA383586-69E3-40EB-A20D-AC1AD24E336C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteStandardBase", "SaxoniteStandardBase\SaxoniteStandardBase.csproj", "{38FBE932-C1D0-4A28-9172-1466347FD62B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardBase", "..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj", "{9C5309DA-3205-4423-BE13-923EE61370A6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardDbas", "..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj", "{E211FE36-108E-4D07-BD30-799A156DDDBB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardMob", "..\..\Common-VS\Workspace\SacsStandardMob\SacsStandardMob.csproj", "{53A962E2-F26B-4DAD-B0F0-EA0B24B520A1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteStandardMob", "SaxoniteStandardMob\SaxoniteStandardMob.csproj", "{89CFDAA1-15C9-45CF-8FBF-88E36E0E32CF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteCoreWeb", "SaxoniteCoreWeb\SaxoniteCoreWeb.csproj", "{CFF91B48-04B7-4075-A5B2-FF3FC0B22BC1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteCoreWebOnline", "SaxoniteCoreWebOnline\SaxoniteCoreWebOnline.csproj", "{C1A3E949-B5EA-4A77-9411-5D8B350056D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsCoreWeb", "..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj", "{BBEEBD5B-7D00-4024-9626-D8B2FBF248A4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SaxoniteCoreUnitTests", "SaxoniteCoreUnitTests\SaxoniteCoreUnitTests.csproj", "{CA680625-E334-4F76-881B-1327A2F9FE86}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution files", "Solution files", "{BD66A338-2D41-4B64-86B0-670F4F42A489}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F646283D-F7C2-42DD-AB1E-E741455664BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F646283D-F7C2-42DD-AB1E-E741455664BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F646283D-F7C2-42DD-AB1E-E741455664BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F646283D-F7C2-42DD-AB1E-E741455664BD}.Release|Any CPU.Build.0 = Release|Any CPU
{DA383586-69E3-40EB-A20D-AC1AD24E336C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA383586-69E3-40EB-A20D-AC1AD24E336C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA383586-69E3-40EB-A20D-AC1AD24E336C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA383586-69E3-40EB-A20D-AC1AD24E336C}.Release|Any CPU.Build.0 = Release|Any CPU
{38FBE932-C1D0-4A28-9172-1466347FD62B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38FBE932-C1D0-4A28-9172-1466347FD62B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38FBE932-C1D0-4A28-9172-1466347FD62B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38FBE932-C1D0-4A28-9172-1466347FD62B}.Release|Any CPU.Build.0 = Release|Any CPU
{9C5309DA-3205-4423-BE13-923EE61370A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C5309DA-3205-4423-BE13-923EE61370A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C5309DA-3205-4423-BE13-923EE61370A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C5309DA-3205-4423-BE13-923EE61370A6}.Release|Any CPU.Build.0 = Release|Any CPU
{E211FE36-108E-4D07-BD30-799A156DDDBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E211FE36-108E-4D07-BD30-799A156DDDBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E211FE36-108E-4D07-BD30-799A156DDDBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E211FE36-108E-4D07-BD30-799A156DDDBB}.Release|Any CPU.Build.0 = Release|Any CPU
{53A962E2-F26B-4DAD-B0F0-EA0B24B520A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53A962E2-F26B-4DAD-B0F0-EA0B24B520A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53A962E2-F26B-4DAD-B0F0-EA0B24B520A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53A962E2-F26B-4DAD-B0F0-EA0B24B520A1}.Release|Any CPU.Build.0 = Release|Any CPU
{89CFDAA1-15C9-45CF-8FBF-88E36E0E32CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89CFDAA1-15C9-45CF-8FBF-88E36E0E32CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89CFDAA1-15C9-45CF-8FBF-88E36E0E32CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89CFDAA1-15C9-45CF-8FBF-88E36E0E32CF}.Release|Any CPU.Build.0 = Release|Any CPU
{CFF91B48-04B7-4075-A5B2-FF3FC0B22BC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CFF91B48-04B7-4075-A5B2-FF3FC0B22BC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CFF91B48-04B7-4075-A5B2-FF3FC0B22BC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CFF91B48-04B7-4075-A5B2-FF3FC0B22BC1}.Release|Any CPU.Build.0 = Release|Any CPU
{C1A3E949-B5EA-4A77-9411-5D8B350056D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1A3E949-B5EA-4A77-9411-5D8B350056D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1A3E949-B5EA-4A77-9411-5D8B350056D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1A3E949-B5EA-4A77-9411-5D8B350056D6}.Release|Any CPU.Build.0 = Release|Any CPU
{BBEEBD5B-7D00-4024-9626-D8B2FBF248A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBEEBD5B-7D00-4024-9626-D8B2FBF248A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBEEBD5B-7D00-4024-9626-D8B2FBF248A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBEEBD5B-7D00-4024-9626-D8B2FBF248A4}.Release|Any CPU.Build.0 = Release|Any CPU
{CA680625-E334-4F76-881B-1327A2F9FE86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA680625-E334-4F76-881B-1327A2F9FE86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA680625-E334-4F76-881B-1327A2F9FE86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA680625-E334-4F76-881B-1327A2F9FE86}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0DFC2321-53B9-493B-83D3-2ACD1C47C732}
EndGlobalSection
EndGlobal
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<RootNamespace>Saxonite.Net.Core</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
<ProjectReference Include="..\SaxoniteCoreWebAdmin\SaxoniteCoreWebAdmin.csproj" />
<ProjectReference Include="..\SaxoniteCoreWeb\SaxoniteCoreWeb.csproj" />
<ProjectReference Include="..\SaxoniteStandardBase\SaxoniteStandardBase.csproj" />
<ProjectReference Include="..\SaxoniteStandardDbas\SaxoniteStandardDbas.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.UnitTests.Data.Population
{
//=======================================================================
[TestClass]
class SaxoniteRegionPopulation_Tester
{
}
//=======================================================================
}
@@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Saxonite.Net.Core.Web.Admin.App;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.UnitTests.Web.Admin.App
{
//=======================================================================
[TestClass]
public class SaxApplication_Admin_Tester
{
//-------------------------------------------------------------------
// Application.
private class SaxApplication_Admin_Test : SaxApplication_Admin
{
public SaxApplication_Admin_Test(IConfiguration configuration, IWebHostEnvironment env) :
base(configuration)
{
InitEnvironment(env);
}
}
private static SaxApplication_Admin_Test app;
public static SaxApplication_Admin App
{
get
{
if (app == null)
{
var mockConfiguration = new Mock<IConfiguration>();
var mockEnvironment = new Mock<IWebHostEnvironment>();
mockEnvironment.Setup(m => m.EnvironmentName).Returns(Environments.Development);
mockEnvironment.Setup(m => m.ContentRootPath).Returns(@"D:\Sarand\Projects\Users\AHoward\VS2019\Saxonite\Workspace\SaxoniteCoreWebAdmin");
app = new SaxApplication_Admin_Test(mockConfiguration.Object, mockEnvironment.Object);
}
return app;
}
}
//-------------------------------------------------------------------
// Internals - component managers etc.
private static SaxInternals internals;
public static SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(App.SessionContext); return internals; } }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,79 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Sarand.Net.Standard.Base.Bus;
using Saxonite.Net.Core.Web.Admin.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Mgr;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.UnitTests.Web.Admin.App
{
//=======================================================================
[TestClass]
public class SaxSessionData_Tester
{
//-------------------------------------------------------------------
// Properties.
public static SaxApplication_Admin App { get { return SaxApplication_Admin_Tester.App; } }
public static SaxInternals Internals { get { return SaxApplication_Admin_Tester.Internals; } }
//-------------------------------------------------------------------
[TestMethod]
public void Test_SmmSessionData_Serialization()
{
// User account.
SaxAccountMgr accountMgr = Internals.AccountMgr;
// User account.
SaxUserAccount userAccount = accountMgr.CreateUserAccount();
userAccount.Id = new SysId(45678);
userAccount.EmailAddress = "john.smith@acme.co.uk";
userAccount.FirstName = "John";
userAccount.LastName = "Smith";
// User Roles.
List<SaxUserRole> userRoles = new List<SaxUserRole>();
SaxUserRole userRole = accountMgr.CreateUserRole(userAccount.Id, Sax.UserRole.Journalist);
userRoles.Add(userRole);
// Create user session and verify user id.
SaxSessionData_Admin sessionData = new SaxSessionData_Admin(App.SessionContext, userAccount, userRoles);
Assert.AreEqual(SysId.ToInt(sessionData.User.UserAccountId), SysId.ToInt(sessionData.User.UserAccountId));
Assert.AreEqual(userAccount.FirstName, sessionData.User.FirstName);
Assert.AreEqual(userAccount.LastName, sessionData.User.LastName);
Assert.IsTrue(sessionData.ValidUserSession);
Assert.AreNotEqual("", sessionData.User.DisplayName);
// Serialise session data using same method as base controller.
String sessionDataJson = JsonConvert.SerializeObject(sessionData);
Assert.AreNotEqual("", sessionDataJson);
// Deserialise session data using same method as base controller.
SaxSessionData_Admin sessionDataDeserialized =
JsonConvert.DeserializeObject<SaxSessionData_Admin>(
sessionDataJson,
new JsonSerializerSettings { ContractResolver = new SaxController_Base.SessionStorage.SaxSessionDataContractResolver() }
);
Assert.IsNotNull(sessionDataDeserialized);
// Validate deserialised values.
Assert.AreEqual(App.SessionContext.Index, sessionDataDeserialized.SessionContext.Index);
Assert.AreEqual(Sax.Application.Admin, sessionDataDeserialized.SessionContext.Application);
Assert.AreEqual(SysId.ToInt(sessionDataDeserialized.User.UserAccountId), SysId.ToInt(sessionDataDeserialized.User.UserAccountId));
Assert.AreEqual(SysId.ToInt(sessionData.User.UserAccountId), SysId.ToInt(sessionDataDeserialized.User.UserAccountId));
Assert.AreEqual(sessionData.User.EmailAddress, sessionDataDeserialized.User.EmailAddress);
Assert.AreEqual(sessionData.User.FirstName, sessionDataDeserialized.User.FirstName);
Assert.AreEqual(sessionData.User.LastName, sessionDataDeserialized.User.LastName);
Assert.AreEqual(sessionData.User.DisplayName, sessionDataDeserialized.User.DisplayName);
// User roles.
Assert.AreEqual(userRoles.Count, sessionDataDeserialized.User.Roles.Count);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,95 @@
using Microsoft.Extensions.Configuration;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.IO;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Dbas.Data;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public abstract class SaxApplication_Web : SaxApplication_Base
{
//-------------------------------------------------------------------
// Data members.
protected Sax.InstanceType instanceType;
protected String environmentName = "";
//-------------------------------------------------------------------
// Protected construction.
protected SaxApplication_Web()
{
WebApp = this;
}
//-------------------------------------------------------------------
// Properties.
public override SaxSystem System { get { return System_Web; } }
public SaxSystem_Web System_Web { get; protected set; }
public static SaxApplication_Web WebApp { get; private set; }
// Application instance.
public override Sax.InstanceType InstanceType { get { return instanceType; } }
public override String EnvironmentName { get { return environmentName; } }
public bool Development { get; protected set; }
public String LoggingRootFolder { get; protected set; } = "";
// Application components.
public abstract String SessionCookieName { get; }
public virtual SaxSessionContext SessionContext { get; protected set; }
public abstract SaxSessionConfig SessionConfig { get; }
public override SaxConsoleHelper ConsoleHelper { get { return ConsoleHelper_Web; } }
public SaxConsoleHelper_Web ConsoleHelper_Web { get; protected set; }
public override IDatabaseInfo CreateCoreDbInfo(SaxSessionContext sessionContext) { return new DbDataItem.DatabaseInfo(GetCoreDatabase(sessionContext)); }
// Configuration.
public IConfiguration Configuration { get; protected set; }
public IConfiguration SaxConfiguration { get; protected set; }
//-------------------------------------------------------------------
// Logging.
protected void LogApplicationStart(String fn)
{
// Log application start.
List<StringPair> applicationParams = new List<StringPair>();
applicationParams.Add("Host", SaxSystem.ComputerName);
applicationParams.Add("Version", Sax.Version.GetFullVersion());
applicationParams.Add("Instance", InstanceType.ToString() + (!StringUtil.IsEmpty(SessionConfig.Instance) ? " [" + SessionConfig.Instance + "]" : ""));
applicationParams.Add("Configuration", SaxSystem.DebugMode ? "Debug" : "Release");
applicationParams.Add("Environment", EnvironmentName);
applicationParams.Add("Database", SessionConfig.CoreDatabase.Location);
applicationParams.Add("Application root", SaxSystem.ApplicationFolder);
if (!StringUtil.IsEmpty(SessionConfig.Documents.RootFolder))
applicationParams.Add("Documents root", SessionConfig.Documents.RootFolder);
applicationParams.Add("Email context", SessionConfig.Email.Context + (!StringUtil.IsEmpty(EmailConfig.SMTP.Server) ? " [" + EmailConfig.SMTP.Server + "]" : ""));
SaLogger.Info(fn + " Application start." + applicationParams.Select(item => StringUtil.PadRight(item.Item1 + ":", 20, ' ') + item.Item2).Concat("", "\n\t"), true);
}
//-------------------------------------------------------------------
protected void InitialiseTemporaryLoggingFolder()
{
// Default logging folder - in case of startup failure.
try
{
foreach (String folder in new String[] { @"D:\Sarand\Projects\User\AHoward\VS2017\Saxonite\Logs" })
{
if (DirectoryUtil.DirectoryExists(folder))
{
SaLogger.LogFolder = folder;
break;
}
}
}
catch (Exception) { }
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,21 @@
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public static class SaxGlobals_Web
{
//-------------------------------------------------------------------
// System.
public static SaxSystem_Web System { get { return SaxGlobals_Web.System as SaxSystem_Web; } }
//-------------------------------------------------------------------
// Application.
//-------------------------------------------------------------------
}
//=======================================================================
}
+186
View File
@@ -0,0 +1,186 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Web;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public abstract class SaxLocaliser
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
/// <summary>
/// Localisers are supplied by derived object.
/// </summary>
protected SaxLocaliser()
{
}
//-------------------------------------------------------------------
// Properties.
public abstract IStringLocalizer MainProperties { get; }
public virtual List<IStringLocalizer> AlternativeProperties { get; } = new List<IStringLocalizer>();
//-------------------------------------------------------------------
// Generic phrase translation.
//-------------------------------------------------------------------
/// <summary>Retrieve localised text.</summary>
public LocalizedString GetText(string key)
{
return GetText(key, new object[] { });
}
public LocalizedString GetText(string key, object param)
{
return GetText(key, new object[] { param });
}
public LocalizedString GetText(string key, object param1, object param2)
{
return GetText(key, new object[] { param1, param2 });
}
public LocalizedString GetText(string key, object[] parameters)
{
LocalizedString text = MainProperties.GetString(key, parameters);
if (text.ResourceNotFound)
{
SaLogger.Debug(GetType().Name + ".GetText Key not found: " + key);
// Look up key in alternative property files.
foreach (IStringLocalizer properties in AlternativeProperties ?? new List<IStringLocalizer>())
{
text = properties.GetString(key, parameters);
if (!text.ResourceNotFound)
break;
}
}
return text;
}
// Square bracket indexing.
public LocalizedString this[String key]
{
get { return GetText(key); }
}
public LocalizedString this[String key, object param]
{
get { return GetText(key, param); }
}
public LocalizedString this[String key, object param1, object param2]
{
get { return GetText(key, param1, param2); }
}
//-------------------------------------------------------------------
/// <summary>
/// Localised HTML text.
/// <para>Line delimiters changed to '<br/>'.</para>
/// </summary>
public HtmlString GetHtmlText(string key, String param1 = "", String param2 = "")
{
return GetHtmlText(key, new Object[] { param1, param2 });
}
/// <summary>
/// Localised HTML text.
/// <para>Line delimiters changed to '<br/>'.</para>
/// </summary>
public HtmlString GetHtmlText(string key, object[] parameters)
{
LocalizedString localisedText = GetText(key, parameters);
String value = localisedText.Value;
// Replace all line delimiters.
value = value.Replace("\r\n", "\n").Replace("\n", "<br/>").Replace("\r", "<br/>");
return new HtmlString(value);
}
//-------------------------------------------------------------------
/// <summary>
/// Localised javascript string.
/// <para>All quotes are escaped.</para>
/// </summary>
public LocalizedString GetJavascriptText(string key, String param1 = "", String param2 = "")
{
LocalizedString localisedText = GetText(key, param1, param2);
return new LocalizedString(localisedText, EscapeJavascriptText(localisedText.Value), localisedText.ResourceNotFound);
}
public String EscapeJavascriptText(string value)
{
// Escape all quotes.
value = value.Replace("'", "\\'").Replace("\"", "\\\"");
return value;
}
//-------------------------------------------------------------------
// Specific phrase translation.
//-------------------------------------------------------------------
// Status.
public String GetStatusCaption(SaStatus status)
{
// ToDo Status description localisation.
return SacsSystem.GetStatusDesc(status);
}
// Region type.
private List<TypeCodeDesc> regionTypes;
public String GetRegionTypeCaption(Sax.RegionType regionType)
{
if ((int)regionType < 1) return "";
if (regionTypes == null)
regionTypes = SaxSystem.GetCodesOfType(Sax.CodeType.RegionType);
// ToDo Region type localisation.
return regionTypes.GetDescForType(regionType);
}
// User role.
private List<TypeCodeDesc> userRoles;
public String GetUserRoleCaption(Sax.UserRole userRole)
{
if ((int)userRole < 1) return "";
if (userRoles == null)
userRoles = SaxSystem.GetCodesOfType(Sax.CodeType.UserRole);
// ToDo User Role localisation.
return userRoles.GetDescForType(userRole);
}
// Organisation role.
private List<TypeCodeDesc> organisationRoles;
public String GetOrganisationRoleCaption(Sax.OrganisationRole organisationRole)
{
if ((int)organisationRole < 1) return "";
if (organisationRoles == null)
organisationRoles = SaxSystem.GetCodesOfType(Sax.CodeType.OrganisationRole);
// ToDo Organisation Role localisation.
return organisationRoles.GetDescForType(organisationRole);
}
//-------------------------------------------------------------------
// Region.
public String GetRegionName(SaxRegion region)
{
if (region == null) return "";
// ToDo Region name localisation.
return region.Name;
}
//-------------------------------------------------------------------
// Sport.
public String GetSportName(SaxSport sport)
{
if (sport == null) return "";
// ToDo Sport name localisation.
return sport.Name;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,200 @@
using Microsoft.Extensions.Configuration;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public class SaxSessionConfig
{
//---------------------------------------------------------------
// Data members.
private readonly List<String> environments = new List<string>();
//---------------------------------------------------------------
// Construction.
public SaxSessionConfig (String context, ConfigurationSection configSection, SaxSessionConfig defaultConfig)
{
Context = context;
populateDefaults(defaultConfig);
PopulateConfig(configSection);
}
//---------------------------------------------------------------
private void populateDefaults(SaxSessionConfig defaultConfig)
{
if (defaultConfig == null) return;
Security.Password.MinLength = defaultConfig.Security.Password.MinLength;
Security.SignIn.KeepLoggedIn.Allowed = defaultConfig.Security.SignIn.KeepLoggedIn.Allowed;
Security.SignIn.KeepLoggedIn.CookieExpiresDays = defaultConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays;
}
private void PopulateConfig(ConfigurationSection configSection)
{
if (configSection != null)
{
// Environments.
foreach (ConfigurationSection environment in configSection.GetSection("Environments").GetChildren())
environments.Add(StringUtil.TrimAll(environment.Value));
Instance = StringUtil.OnEmpty(configSection["Instance"], Instance);
// Security.
IConfigurationSection security = configSection.GetSection("Security");
if (security.Exists())
{
// Security - Password.
IConfigurationSection securityPassword = security.GetSection("Password");
if (securityPassword.Exists())
{
Security.Password.MinLength = StringUtil.ToInt(securityPassword["MinLength"], Security.Password.MinLength);
}
// Security - Sign-Up.
IConfigurationSection securitySignUp = security.GetSection("SignUp");
if (securitySignUp.Exists())
{
}
// Security - Sign-In.
IConfigurationSection securitySignIn = security.GetSection("SignIn");
if (securitySignIn.Exists())
{
IConfigurationSection keepLoggedIn = securitySignIn.GetSection("SignIn");
if (keepLoggedIn.Exists())
{
Security.SignIn.KeepLoggedIn.Allowed = StringUtil.ToBool(StringUtil.OnEmpty(keepLoggedIn["Allowed"], Security.SignIn.KeepLoggedIn.Allowed.ToString()));
Security.SignIn.KeepLoggedIn.CookieExpiresDays = StringUtil.ToInt(keepLoggedIn["CookieExpiresDays"], Security.SignIn.KeepLoggedIn.CookieExpiresDays);
}
}
}
// Email.
IConfigurationSection email = configSection.GetSection("Email");
if (email.Exists())
{
Email.Context = email["Context"];
PopulateEmailConfig(Email.ContactUs, email.GetSection("ContactUs"));
PopulateEmailConfig(Email.Signup_Activate, email.GetSection("Signup").GetSection("Activate"));
PopulateEmailConfig(Email.Signup_Welcome, email.GetSection("Signup").GetSection("Welcome"));
}
// Documents.
IConfigurationSection documents = configSection.GetSection("Documents");
if (documents.Exists())
{
Documents.RootFolder = StringUtil.OnEmpty(documents["RootFolder"], Documents.RootFolder);
}
// Logging.
IConfigurationSection logging = configSection.GetSection("Logging");
if (logging.Exists())
{
Logging.RootFolder = StringUtil.OnEmpty(logging["RootFolder"], Logging.RootFolder);
}
// Database.
extractDatabaseConfig(CoreDatabase, configSection.GetSection("CoreDatabase"));
// Variants.
foreach (ConfigurationSection variantSection in configSection.GetSection("Variants").GetChildren())
{
// Variants override selected values but must match the application root folder to identify a specific instance.
String applicationRootPath = variantSection["ApplicationRootFolder"];
if (StringUtil.IsEmpty(applicationRootPath)) continue;
if (!SaxSystem.ApplicationFolder.EqualsCI(applicationRootPath)) continue;
PopulateConfig(variantSection);
}
}
}
private void PopulateEmailConfig(EmailConfig.SpecificEmailConfig config, IConfigurationSection emailSection)
{
if (!emailSection.Exists()) return;
foreach (var addressTo in emailSection.GetSection("AddressesTo").GetChildren())
config.AddressesTo.Add(StringUtil.TrimAll(addressTo.Value));
foreach (var addressCc in emailSection.GetSection("AddressesCc").GetChildren())
config.AddressesCc.Add(StringUtil.TrimAll(addressCc.Value));
foreach (var addressBcc in emailSection.GetSection("AddressesBcc").GetChildren())
config.AddressesBcc.Add(StringUtil.TrimAll(addressBcc.Value));
}
private void extractDatabaseConfig(DatabaseConfig database, IConfigurationSection databaseSection)
{
if (!databaseSection.Exists()) return;
database.Location = StringUtil.OnEmpty(databaseSection["Location"], database.Location);
database.User = StringUtil.OnEmpty(databaseSection["User"], database.User);
database.Password = StringUtil.OnEmpty(databaseSection["Password"], database.Password);
}
//---------------------------------------------------------------
// Properties.
public String Context { get; }
public List<String> Environments { get { return new List<String>(environments); } }
public String Instance { get; set; } = "";
// Security.
public class SecurityConfig
{
public class PasswordConfig
{
public int MinLength { get; set; }
}
public PasswordConfig Password { get; } = new PasswordConfig();
public class SignUpConfig
{
public class ActivationConfig
{
public int EmailLinkExpiresMins = Sax.Config.Security.Activation.EmailLinkExpiresMins_Default;
}
public ActivationConfig Activation { get; } = new ActivationConfig();
}
public SignUpConfig SignUp { get; } = new SignUpConfig();
public class SignInConfig
{
public class PasswordRequestConfig
{
public int EmailLinkExpiresMins = Sax.Config.Security.Activation.EmailLinkExpiresMins_Default;
}
public PasswordRequestConfig PasswordRequest { get; } = new PasswordRequestConfig();
public class KeepLoggedInConfig
{
public bool Allowed { get; set; } = Sax.Config.Security.SignIn.KeepLoggedIn.Allowed_Default;
public int CookieExpiresDays { get; set; } = Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Default;
}
public KeepLoggedInConfig KeepLoggedIn { get; } = new KeepLoggedInConfig();
}
public SignInConfig SignIn { get; } = new SignInConfig();
}
public SecurityConfig Security { get; } = new SecurityConfig();
// Email.
public class EmailConfig
{
public class SpecificEmailConfig
{
public List<String> AddressesTo { get; } = new List<string>();
public List<String> AddressesCc { get; } = new List<string>();
public List<String> AddressesBcc { get; } = new List<string>();
}
public SpecificEmailConfig ContactUs { get; } = new SpecificEmailConfig();
public SpecificEmailConfig Signup_Activate { get; } = new SpecificEmailConfig();
public SpecificEmailConfig Signup_Welcome { get; } = new SpecificEmailConfig();
public String Context { get; internal set; }
}
public EmailConfig Email { get; } = new EmailConfig();
// Databases.
public class DatabaseConfig
{
public String Location { get; internal set; }
public String User { get; internal set; }
public String Password { get; internal set; }
}
public DatabaseConfig CoreDatabase { get; } = new DatabaseConfig();
// Documents.
public class DocumentsConfig
{
public String RootFolder { get; internal set; }
}
public DocumentsConfig Documents { get; } = new DocumentsConfig();
// Logging.
public class LoggingConfig
{
public String RootFolder { get; internal set; }
}
public LoggingConfig Logging { get; } = new LoggingConfig();
//---------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,23 @@
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public class SaxSessionContext_Web : SaxSessionContext
{
//-------------------------------------------------------------------
/// <summary>Blank construction - used for deserilisation of session data.</summary>
protected SaxSessionContext_Web () : base() { }
public SaxSessionContext_Web (ushort sesionContextIndex, Sax.Application application) :
base(sesionContextIndex, application)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,22 @@
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public class SaxSessionData_Web : SaxSessionData
{
//-------------------------------------------------------------------
// Protected construction.
protected SaxSessionData_Web(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles) :
base(sessionContext, userAccount, userRoles)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
+199
View File
@@ -0,0 +1,199 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Saxonite.Net.Core.Web.Base.Middleware;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public class SaxStartup
{
//-------------------------------------------------------------------
// Data members.
// Localisation.
private static RequestCulture defaultRequestCulture = new RequestCulture("en-GB", "en-GB");
private static List<CultureInfo> supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-GB"),
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
};
//-------------------------------------------------------------------
// Protected construction.
protected SaxStartup(IConfiguration configuration)
{
Configuration = configuration;
}
//-------------------------------------------------------------------
// Properties.
public IConfiguration Configuration { get; }
public static RequestCulture DefaultRequestCulture { get { return defaultRequestCulture; } }
public static List<CultureInfo> SupportedCultures { get { return new List<CultureInfo>(supportedCultures); } }
//-------------------------------------------------------------------
// This method gets called by the runtime. Use this method to add services to the container.
protected class ConfigureServicesConfig
{
public IServiceCollection services;
public String sessionCookieName;
public bool sessionCookieHttpOnly;
public TimeSpan sessionIdleTimeout;
}
protected void ConfigureServices(ConfigureServicesConfig config)
{
// Added to support memory caching of sessions.
config.services.AddMemoryCache();
//config.services.AddDistributedMemoryCache(); - for multiple web servers.
// Online web application cookie definition.
config.services.AddSession
(options =>
{
options.Cookie.Name = config.sessionCookieName;
options.Cookie.HttpOnly = config.sessionCookieHttpOnly;
options.IdleTimeout = config.sessionIdleTimeout;
// Override cookie policy for session cookie. Ref. config.app.UseCookiePolicy()
options.Cookie.IsEssential = true;
}
);
// Localisation properties folder.
String resourcesPath = "Localisation";
config.services.AddLocalization(opts => { opts.ResourcesPath = resourcesPath; });
// Modify pages during debug.
#if DEBUG
if (SaxSystem.DebugMode)
// If the following line throws an error such as:
// 'imvcbuilder' does not contain a definition for 'addrazorruntimecompilation'
// Revert the NuGet Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation package
// to a previous version and then reinstal the latest version.
config.services.AddRazorPages().AddRazorRuntimeCompilation();
#endif
config.services.AddMvc
(options =>
{
// Model binding - intercepting form values being bound to models.
options.ModelBinderProviders.Insert(0, new SaxModelBinderProvider());
// Blank form values to be populated to model as empty strings rather than null values.
options.ModelMetadataDetailsProviders.Add(new SaxMetadataProvider());
}
)
// View and data annotation localisations.
.AddViewLocalization(
LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = resourcesPath; })
.AddDataAnnotationsLocalization();
// Configure localisation middleware.
config.services.Configure<RequestLocalizationOptions>
(opts =>
{
// Default culture to use for requests.
opts.DefaultRequestCulture = DefaultRequestCulture;
// Cultures supported by the application. Formatting numbers, dates, etc.
opts.SupportedCultures = SupportedCultures;
// UI cultures supported by the application. UI strings that have been localized.
opts.SupportedUICultures = SupportedCultures;
}
);
// Allows access to the HttpContext inside a service.
config.services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
//-------------------------------------------------------------------
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
protected class ConfigureConfig
{
public IApplicationBuilder app;
public IHostApplicationLifetime hostApplicationLifetime;
public IWebHostEnvironment env;
}
protected void Configure(ConfigureConfig config)
{
if (config.env.IsDevelopment())
{
config.app.UseDeveloperExceptionPage();
}
else
{
config.app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
config.app.UseHsts();
}
config.app.UseHttpsRedirection();
config.app.UseStaticFiles();
config.app.UseCookiePolicy();
config.app.UseSession();
config.app.UseRouting();
//config.app.UseAuthorization();
config.app.UseAntiforgeryTokens();
config.app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
// Application events.
// NB The shutdown events only fire if he application stops 'gracefully'.
config.hostApplicationLifetime.ApplicationStarted.Register(OnApplicationStarted);
config.hostApplicationLifetime.ApplicationStopping.Register(OnApplicationStopping);
config.hostApplicationLifetime.ApplicationStopped.Register(OnApplicationStopped);
config.app.Use(async (context, next) =>
{
if (context.Request.Method.Equals(HttpMethod.Get.Method))
{
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
//context.Response.Headers[HeaderNames.Vary] = new string[] { "Accept-Encoding" };
}
await next();
});
}
//-------------------------------------------------------------------
// Application events.
private readonly static String ApplicationEventLogPrefix = "### ";
private void OnApplicationStarted()
{
SaLogger.Info(ApplicationEventLogPrefix + "Application started");
}
private void OnApplicationStopping()
{
SaLogger.Info(ApplicationEventLogPrefix + "Application stopping");
}
private void OnApplicationStopped()
{
SaLogger.Info(ApplicationEventLogPrefix + "Application stopped");
}
//-------------------------------------------------------------------
}
//=======================================================================
}
+135
View File
@@ -0,0 +1,135 @@
using Sarand.Net.Standard.Base.Core;
using Saxonite.Net.Standard.Dbas.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Mgr;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
namespace Saxonite.Net.Core.Web.Base.App
{
//=======================================================================
public class SaxSystem_Web : SaxSystem_Dbas
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
/// <summary>Static initialisation.</summary>
static SaxSystem_Web()
{
InitComputerName();
InitUserName();
InitApplicationPath();
InitApplicationFolder();
InitAssemblyBuildAt();
}
//-------------------------------------------------------------------
// Construction.
public SaxSystem_Web() : base()
{
}
//-------------------------------------------------------------------
/// <summary>
/// Computer name
/// <para>Windows: System.Environment.MachineName</para>
/// <para>Web: HttpContext.Current.Server.MachineName</para>
/// <para>FQDN: System.Net.Dns.GetHostName()</para>
/// </summary>
private static void InitComputerName()
{
try
{
ComputerName = System.Environment.MachineName;
}
catch (Exception exc)
{
SaLogger.Warn("SaxSystem.InitComputerName Failed to initialise computer name from environment. " + ExceptionUtil.GetErrorStateMessage(exc));
}
try
{
if (StringUtil.IsEmpty(ComputerName))
ComputerName = Environment.GetEnvironmentVariable("CUMPUTERNAME") ?? Environment.GetEnvironmentVariable("HOSTNAME");
}
catch (Exception exc)
{
SaLogger.Warn("SaxSystem.InitComputerName Failed to initialise computer name from environment variable. " + ExceptionUtil.GetErrorStateMessage(exc));
}
}
//-------------------------------------------------------------------
/// <summary>To be populated by application<para>Windows: Environment.UserName</para></summary>
private static void InitUserName()
{
try
{
UserName = Environment.UserName;
}
catch (Exception exc)
{
SaLogger.Warn("SaxSystem.InitUserName Failed to initialise user name. " + ExceptionUtil.GetErrorStateMessage(exc));
}
}
//-------------------------------------------------------------------
/// <summary>
/// Application Path.
/// <para>Windows: System.Reflection.Assembly.GetEntryAssembly().Location;</para>
/// </summary>
private static void InitApplicationPath()
{
try
{
ApplicationPath = Assembly.GetEntryAssembly().Location;
}
catch (Exception exc)
{
SaLogger.Warn("SaxSystem.InitApplicationPath Failed to initialise application path. " + ExceptionUtil.GetErrorStateMessage(exc));
}
}
//-------------------------------------------------------------------
/// <summary>
/// Application folder.
/// <para>Windows: System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);</para>
/// </summary>
private static void InitApplicationFolder()
{
try
{
ApplicationFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
}
catch (Exception exc)
{
SaLogger.Warn("SaxSystem.InitApplicationFolder Failed to initialise application folder. " + ExceptionUtil.GetErrorStateMessage(exc));
}
}
//-------------------------------------------------------------------
/// <summary>
/// DateTime when the executing assembly was built.
/// </summary>
private static void InitAssemblyBuildAt()
{
try
{
// Assembly build date.
Assembly assembly = Assembly.GetExecutingAssembly();
AssemblyBuildAt = File.GetLastWriteTime(assembly.Location);
}
catch (Exception exc)
{
SaLogger.Warn("SaxSystem.InitAssemblyBuildAt Failed to initialise assembly build date. " + ExceptionUtil.GetErrorStateMessage(exc));
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,20 @@
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public abstract class SaxConsoleHelper_Web : SaxConsoleHelper
{
//-------------------------------------------------------------------
// Protected construction.
protected SaxConsoleHelper_Web()
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,184 @@
using Microsoft.IdentityModel.Tokens;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
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.Text;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public class SaxDataCacheConsole_Web : SaxDataCacheConsole
{
//-------------------------------------------------------------------
// Data members.
private readonly SaxInternals internals;
//-------------------------------------------------------------------
// Protected construction.
protected SaxDataCacheConsole_Web(SaxSessionContext sessionContext) :
base(sessionContext)
{
internals = new SaxInternals(sessionContext);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
internals.Dispose();
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Regions.
public bool RegionsSupported { get; protected set; } = false;
private class RegionsData
{
public Dictionary<SysId, SaxRegion> regions = null;
public List<SaxRegion> activeRegions = null;
}
private readonly RegionsData regionsData = new RegionsData();
public override List<SaxRegion> GetActiveRegions()
{
String fn = GetType().Name + ".ActiveRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
List<SaxRegion> activeRegions = new List<SaxRegion>();
lock (regionsData)
{
if (regionsData.regions == null) throw new NotSupportedException(fn + " Regions not populated.");
activeRegions = new List<SaxRegion>(regionsData.activeRegions);
}
return activeRegions;
}
private bool IsRegionActive(SaxRegion region)
{
if (region == null) return false;
if (!region.IsStatusActive) return false;
if (SysId.IsValid(region.RegionIdParent))
return IsRegionActive(regionsData.regions.GetValue(region.RegionIdParent, null));
return true;
}
protected void RefreshRegions()
{
String fn = GetType().Name + ".RefreshRegions";
if (!RegionsSupported) throw new NotSupportedException(fn + " Regions not supported.");
SaLogger.Info(fn + " START.", false);
DateTime startedAt = DateTime.Now;
List<SaxRegion> oldRegions = null;
Dictionary<SysId, SaxRegion> newRegions = new Dictionary<SysId, SaxRegion>();
// Query Regions.
SaxRegion.Params prmRegion = new SaxRegion.Params();
prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted;
foreach (SaxRegion region in internals.ReferenceMgr.GetRegionList(prmRegion))
newRegions[region.Id] = region;
// Replace the list of Regions.
lock (regionsData)
{
oldRegions = regionsData.regions?.GetValues() ?? new List<SaxRegion>();
regionsData.regions = newRegions;
// Populate list of active regions.
regionsData.activeRegions = new List<SaxRegion>();
foreach (SaxRegion region in regionsData.regions.GetValues())
{
if (IsRegionActive(region))
{
// Clone region to ensure internal list isn't modified.
SaxRegion activeRegion = internals.ReferenceMgr.CreateRegion();
activeRegion.SetData(region.GetData());
regionsData.activeRegions.Add(activeRegion);
}
}
SaLogger.Info(fn + " END. Regions found: " + regionsData.regions.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds), true);
}
// Dispose old regions.
oldRegions.ClearAndDispose<SaxRegion>();
oldRegions = null;
}
//-------------------------------------------------------------------
// Sports.
public bool SportsSupported { get; protected set; } = false;
private class SportsData
{
public Dictionary<SysId, SaxSport> sports = null;
public List<SaxSport> activeSports = null;
}
private readonly SportsData sportsData = new SportsData();
public override List<SaxSport> GetActiveSports()
{
String fn = GetType().Name + ".ActiveSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
List<SaxSport> activeSports = new List<SaxSport>();
lock (sportsData)
{
if (sportsData.sports == null) throw new NotSupportedException(fn + " Sports not populated.");
foreach (SaxSport sport in sportsData.sports.GetValues())
{
if (IsSportActive(sport))
{
// Clone sport to ensure internal list isn't modified.
SaxSport activeSport = internals.ReferenceMgr.CreateSport();
activeSport.SetData(sport.GetData());
activeSports.Add(activeSport);
}
}
}
return activeSports;
}
private bool IsSportActive(SaxSport sport)
{
if (sport == null) return false;
if (!sport.IsStatusActive) return false;
return true;
}
protected void RefreshSports()
{
String fn = GetType().Name + ".RefreshSports";
if (!SportsSupported) throw new NotSupportedException(fn + " Sports not supported.");
SaLogger.Info(fn + " START.", false);
DateTime startedAt = DateTime.Now;
List<SaxSport> oldSports = null;
Dictionary<SysId, SaxSport> newSports = new Dictionary<SysId, SaxSport>();
// Query Sports.
SaxSport.Params prmSport = new SaxSport.Params();
prmSport.StatusCat = SaxSport.StatusCat.NotDeleted;
foreach (SaxSport sport in internals.ReferenceMgr.GetSportList(prmSport))
newSports[sport.Id] = sport;
// Replace the list of Sports.
lock (sportsData)
{
oldSports = sportsData.sports?.GetValues() ?? new List<SaxSport>();
sportsData.sports = newSports;
// Populate list of active sports.
sportsData.activeSports = new List<SaxSport>();
foreach (SaxSport sport in sportsData.sports.GetValues())
{
if (IsSportActive(sport))
sportsData.activeSports.Add(sport);
}
SaLogger.Info(fn + " END. Sports found: " + sportsData.sports.Count + ". Elapsed: " + DateUtil.FormatNumberOfSeconds((DateTime.Now - startedAt).TotalSeconds), true);
}
// Dispose old sports.
oldSports.ClearAndDispose<SaxSport>();
oldSports = null;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,21 @@
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Consoles
{
//=======================================================================
public class SaxEmailConsole_Web : SaxEmailConsole
{
//-------------------------------------------------------------------
// Protected construction.
public SaxEmailConsole_Web(SaxEmailConfig emailConfig) :
base(emailConfig)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,497 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
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.Email;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Base.Mgr;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
namespace Saxonite.Net.Core.Web.Base.Controllers
{
//=======================================================================
public abstract class SaxController_Base : Controller
{
//-------------------------------------------------------------------
// Data members.
private SaxInternals internals;
//-------------------------------------------------------------------
// Protected construction.
protected SaxController_Base(SaxLocaliser localiser)
{
Localiser = localiser;
// Initialise console (debug) logging listener.
//if (Directory.Exists(App.LoggingRootFolder) && SaxSystem.DebugMode)
// SaxConsoleListener.ConfigureDebugListener(App.LoggingRootFolder);
if (DebugMode) SaLogger.Trace(GetType().Name + ".CTR");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Properties.
public bool DebugMode { get; } = SacsSystem.DebugMode;
protected SaxApplication_Web App { get { return SaxApplication_Web.WebApp; } }
public SaxSessionConfig SessionConfig { get { return App.SessionConfig; } }
public SaxSessionContext SessionContext { get { return App.SessionContext; } }
protected SaxLocaliser Localiser { get; }
/// <summary>
/// Indicate whether session data should be stored by action.
/// <para>Any action that might modify the session data must store it.</para>
/// <para>Some actions such as scoring a password's complexity don't need to.</para>
/// </summary>
public virtual bool StoreSessionData { get; protected set; } = true;
// Culture.
private string LanguageIso2
{
get
{
var feature = HttpContext.Features.Get<IRequestCultureFeature>();
return feature.RequestCulture.UICulture.TwoLetterISOLanguageName.ToLower();
}
}
// Business components.
public SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
// Consoles.
public SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
// Managers.
public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
public SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
public SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
// System codes.
public List<TypeCodeDesc> RegionTypes { get { return Internals.RegionTypes; } }
public List<TypeCodeDesc> UserRoles { get { return Internals.UserRoles; } }
public List<TypeCodeDesc> OrganisationTypes { get { return Internals.OrganisationTypes; } }
public List<TypeCodeDesc> OrganisationCategories { get { return Internals.OrganisationCategories; } }
public List<TypeCodeDesc> OrganisationRoles { get { return Internals.OrganisationRoles; } }
// Email.
public SaEmailConfig EmailConfig { get { return App.EmailConfig; } }
public SaxEmailConsole EmailConsole { get { return Internals.EmailConsole; } }
//-------------------------------------------------------------------
// Redirection locations.
//-------------------------------------------------------------------
public static readonly String ParamName_SessionExpired = "SessionExpired";
protected enum RedirectLocation
{
WebAppBase,
WebAppHome,
WebAppHome_SessionExpired,
}
protected String GetRedirectLocation(RedirectLocation redirectLocation)
{
string protocol = Request.Scheme;
var parameters = new RouteValueDictionary();
String homeAction = Url.Action("Index", "Home", parameters, protocol);
switch (redirectLocation)
{
case RedirectLocation.WebAppBase:
return Regex.Replace(homeAction, "\\/$", "") + "/";
case RedirectLocation.WebAppHome:
return homeAction;
case RedirectLocation.WebAppHome_SessionExpired:
// Set the session expired parameter so that the page can report it.
// NB Paramater must have a value to be included in the URL query string.
parameters.Add(ParamName_SessionExpired, "1");
return homeAction;
}
return "";
}
//-------------------------------------------------------------------
// Action execution events.
//-------------------------------------------------------------------
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuting" + (ViewData.Model != null ? " Model: " + ViewData.Model : ""));
// Refreh model properties.
SaxModel_Base baseModel = ViewData.Model as SaxModel_Base;
RefreshModel(baseModel);
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuted" + (ViewData.Model != null ? " Model: " + ViewData.Model : ""));
// Refreh model properties.
SaxModel_Base baseModel = ViewData.Model as SaxModel_Base;
RefreshModel(baseModel);
// Store session data if present - as potentially modfied.
if (SessionData != null)
{
// Most actions will store the session data in case it's been modified.
// Some actions (eg. score password) don't need to.
if (StoreSessionData)
{
String sessionDataJson = JsonConvert.SerializeObject(SessionData);
HttpContext.Session.Set(SessionStorage.SessionKey_SessionConfig, SessionStorage.sessionDataEncoding.GetBytes(sessionDataJson));
}
}
else HttpContext.Session.Remove(SessionStorage.SessionKey_SessionConfig);
// Response headers.
if (baseModel != null && baseModel.ProhibitCache)
{
if (Response.Headers.ContainsKey(HeaderNames.CacheControl))
Response.Headers.Remove(HeaderNames.CacheControl);
Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
Response.Headers.Add(HeaderNames.Pragma, "no-cache");
Response.Headers.Add(HeaderNames.Expires, "0");
}
base.OnActionExecuted(filterContext);
}
//-------------------------------------------------------------------
// Model.
//-------------------------------------------------------------------
protected void RefreshModel(SaxModel_Base model)
{
if (model == null) return;
// Session data.
model.SessionData = SessionData;
// Redirect locations.
model.RedirectLocation.Base = GetRedirectLocation(RedirectLocation.WebAppBase);
model.RedirectLocation.Home = GetRedirectLocation(RedirectLocation.WebAppHome);
model.RedirectLocation.Home_SessionExpired = GetRedirectLocation(RedirectLocation.WebAppHome_SessionExpired);
}
//-------------------------------------------------------------------
// Parameters.
//-------------------------------------------------------------------
public String EncryptParameters(NameValueCollection parameters, String base64IV, String base64Key)
{
String fn = nameof(ControllerBase) + ".EncryptParameters";
try
{
// Copy the list of parameters, adding a random name/value pair to randomise the encryption.
NameValueCollection parametersToEncrypt = new NameValueCollection(parameters);
// NB Range 10-189 intended to have equal chance of 2 or 3 digit number.
parametersToEncrypt.Add("!" + SacsRandomHelper.GetInt32(10, 190).ToString(), SacsRandomHelper.GetInt32(10, 190).ToString());
// Sort the keys to ensure that the random '!' param is first in the query string.
String[] paramKeys = parametersToEncrypt.AllKeys;
Array.Sort(paramKeys);
// Serialise parameters as if in a URL query string - so that they can be deserialised in DecryptParameters() below.
string queryString = String.Join("&", paramKeys.Select(a => a + "=" + HttpUtility.UrlEncode(parametersToEncrypt[a])));
String encrypted = SacsEncryptionHelper.Encrypt(queryString, base64IV, base64Key);
if (DebugMode)
{
SaLogger.Debug(fn + " Encrypted: " + encrypted);
NameValueCollection decryptedParams = DecryptParameters(encrypted, base64IV, base64Key);
}
return encrypted;
}
catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return "";
}
public NameValueCollection DecryptParameters(String encrypted, String base64IV, String base64Key)
{
String fn = nameof(ControllerBase) + ".DecryptParameters";
if (DebugMode) SaLogger.Debug(fn + " \n\tEncrypted: " + encrypted + "\n\tIV: " + base64IV + "\n\tKey: " + base64Key);
try
{
String queryString = SacsEncryptionHelper.Decrypt(encrypted, base64IV, base64Key);
return HttpUtility.ParseQueryString(queryString);
}
catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return new NameValueCollection();
}
//-------------------------------------------------------------------
// Cookies.
//-------------------------------------------------------------------
/// <summary>Retrieve the value from the named request cookie.</summary>
public String GetRequestCookie(String key)
{
if (Request.Cookies.ContainsKey(key))
return Request.Cookies[key];
return null;
}
/// <summary>Set the value against the named response cookie.</summary>
public void SetResponseCookie(string key, string value, DateTime? expiresAt = null, bool isEssential = false)
{
CookieOptions options = new CookieOptions();
options.HttpOnly = true;
options.Secure = true;
options.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
if (expiresAt.HasValue)
options.Expires = expiresAt;
if (isEssential)
options.IsEssential = true;
Response.Cookies.Append(key, value, options);
}
//-------------------------------------------------------------------
// Session.
//-------------------------------------------------------------------
public bool ValidUserSession { get { return SessionData?.ValidUserSession ?? false; } }
public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } }
// Referrer.
public String Referer { get { return Request.Headers["Referer"].ToString(); } }
public bool ValidReferer { get { return !StringUtil.IsEmpty(Referer); } }
/// <summary>Store session data, eg. to start a new user session on sign-in.</summary>
protected bool SetSessionData(SaxSessionData sessionData)
{
if (sessionStorage.SessionData != null) return false;
sessionStorage.SessionData = sessionData;
return true;
}
protected void ClearSessionData()
{
if (HttpContext.Session.IsAvailable)
HttpContext.Session.Clear();
sessionStorage.SessionData = null;
}
//-------------------------------------------------------------------
// Session storage.
public class SessionStorage
{
public SaxSessionData SessionData { get; set; }
public static String SessionKey_SessionConfig { get; } = "SaxSessionData";
public static Encoding sessionDataEncoding { get; } = Encoding.UTF8;
//JSON Serialisation.
public class SaxSessionDataContractResolver : DefaultContractResolver
{
// To allow private properties to be deserialised.
// NB Only applies to top level properties in the serialised object.
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
MemberInfo[] fields = objectType.GetFields(flags);
return fields
.Concat(objectType.GetProperties(flags).Where(propInfo => propInfo.CanWrite))
.ToList();
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return base.CreateProperties(type, MemberSerialization.Fields);
}
};
public JsonSerializerSettings SaxSessionDataJsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new SaxSessionDataContractResolver()
};
}
private readonly SessionStorage sessionStorage = new SessionStorage();
protected JsonSerializerSettings SaxSessionDataJsonSerializerSettings { get { return sessionStorage.SaxSessionDataJsonSerializerSettings; } }
//-------------------------------------------------------------------
public SaxSessionData SessionData
{
get
{
try
{
// Retrieve session data if not already in controller.
if (sessionStorage.SessionData == null)
{
byte[] sessionDataBytes = null;
HttpContext.Session.TryGetValue(SessionStorage.SessionKey_SessionConfig, out sessionDataBytes);
if (sessionDataBytes != null)
{
String sessionDataJson = SessionStorage.sessionDataEncoding.GetString(sessionDataBytes);
sessionStorage.SessionData = DeserializeSessionData(sessionDataJson);
}
}
return sessionStorage.SessionData;
}
catch (Exception exc)
{
SaLogger.Warn(GetType().Name + ".SessionData " + exc.GetType().Name + " - " + exc.Message + exc.GetStackEntries().Concat("", "\n\t"));
}
return null;
}
}
protected abstract SaxSessionData DeserializeSessionData(String sessionDataJson);
//-------------------------------------------------------------------
// Ajax actions.
//-------------------------------------------------------------------
protected static readonly Encoding ajaxResponseEncoding = Encoding.UTF8;
protected IActionResult GetAjaxResult_GoHome(AjaxResponse_Base responseData)
{
responseData.SetGoHome();
return Json(responseData);
}
protected IActionResult GetAjaxResult_InvalidSession(AjaxResponse_Base responseData)
{
responseData.SetGoHome();
return Json(responseData);
}
//-------------------------------------------------------------------
public class AjaxResponse_Base
{
// Response status.
public bool Success { get; set; } = false;
public bool Error { get; set; } = false;
public bool SessionExpired { get; protected set; } = false;
public bool GoHome { get; protected set; } = false;
public AjaxResponse_Base SetSessionExpired(String title, String text)
{
SessionExpired = true;
return SetMessage(title, text);
}
public AjaxResponse_Base SetGoHome()
{
GoHome = true;
return this;
}
public AjaxResponse_Base SetGoHome(String title, String text)
{
GoHome = true;
return SetMessage(title, text);
}
// Message - displays alert popup.
public class MessageData
{
public String Title { get; set; }
public String Text { get; set; }
}
public MessageData Message { get; private set; }
public AjaxResponse_Base SetMessage(String title, String text)
{
if (!StringUtil.IsEmpty(text))
Message = new MessageData() { Title = title, Text = text };
return this;
}
}
//-------------------------------------------------------------------
public class AjaxResponse_Data : AjaxResponse_Base
{
// Form data.
public class FormControlValue
{
public FormControlValue(String controlName, String value, String error)
{
ControlName = controlName;
Value = value;
Error = error;
}
public String ControlName { get; }
public String Value { get; set; }
public String Error { get; set; }
}
private readonly Dictionary<String, FormControlValue> controlValues = new Dictionary<String, FormControlValue>();
public List<FormControlValue> ControlValues { get { return controlValues.Values.ToList(); } }
public void SetControlValue(String controlName, String value, String error = null)
{
if (controlValues.ContainsKey(controlName))
{
// Modify existing entry in the list.
FormControlValue controlValue = controlValues[controlName];
if (value != null) controlValue.Value = value;
if (error != null) controlValue.Error = error;
}
else
{
// Add new control to list.
controlValues.Add(controlName, new FormControlValue(controlName, value, error));
}
}
/// <summary>Assign the error to the control by name and return this response data.</summary>
public AjaxResponse_Data SetControlError(String controlName, String error)
{
return SetControlError(controlName, error, this);
}
/// <summary>
/// Assign the error to the control by name and return the specified value.
/// <para>- Allows alternative return values to be specified (eg. 'false') to avoid separate return statement in calling routine.</para>
/// </summary>
public T SetControlError<T>(String controlName, String error, T returnValue)
{
SetControlValue(controlName, null, error);
return returnValue;
}
public bool HasControlErrors()
{
foreach (FormControlValue controlValue in controlValues.Values)
{
if (!StringUtil.IsEmpty(controlValue.Error)) return true;
}
return false;
}
// Notification - briefly displays after response received.
public class NotificationData
{
public String Title { get; set; }
public String Message { get; set; }
}
public NotificationData Notification { get; private set; }
public bool HasNotification { get { return Notification != null; } }
public AjaxResponse_Data SetNotification(String title, String message)
{
Notification = new NotificationData() { Title = title, Message = message };
return this;
}
// Paged data.
public class PagingData
{
public PagingData(SaxModel_Base model) :
this(model.DataPageNo, model.DataPageLength, model.DataItemCount, model.DataItemTotal)
{ }
public PagingData(uint pageNo, ushort pageLength, ushort itemCount, ulong itemTotal)
{
PageNo = pageNo;
PageLength = pageLength;
ItemCount = itemCount;
ItemTotal = itemTotal;
}
public bool Paged { get { return (PageNo > 0 && PageLength > 0 && ItemTotal > PageLength); } }
public uint PageNo { get; } = 0;
public ushort PageLength { get; } = 0;
public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / PageLength) : 0); } }
public ushort ItemCount { get; } = 0;
public ulong ItemTotal { get; } = 0;
}
public PagingData Paging { get; private set; }
public void SetPaging(SaxModel_Base model)
{
Paging = new PagingData(model);
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,94 @@
using Microsoft.Extensions.Configuration;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Email;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Core.Web.Base.App;
using System;
using System.Collections.Generic;
using System.Text;
using Saxonite.Net.Standard.Base.Consoles;
using Sarand.Net.Standard.Base.Utils;
namespace Saxonite.Net.Core.Web.Base.Helpers
{
//=======================================================================
public static class SaxEmailConfigHelper
{
//-------------------------------------------------------------------
/// <summary>Extract the email configurations from the application settings.</summary>
public static Dictionary<String, SaxEmailConfig> ExtractEmailConfigs(IConfigurationSection emailConfigsSection)
{
Dictionary<String, SaxEmailConfig> emailConfigs = new Dictionary<String, SaxEmailConfig>();
foreach (ConfigurationSection emailConfigSection in emailConfigsSection.GetChildren())
{
if (!StringUtil.IsEmpty(emailConfigSection.Key))
emailConfigs.Add(emailConfigSection.Key, CreateEmailConfig(emailConfigSection.Key, emailConfigSection));
}
return emailConfigs;
}
private static SaxEmailConfig CreateEmailConfig(String context, ConfigurationSection emailConfigSection)
{
SaxEmailConfig emailConfig = new SaxEmailConfig(context);
if (emailConfigSection != null)
{
IConfigurationSection message = emailConfigSection.GetSection("Message");
emailConfig.Message.AddressFrom = message["AddressFrom"];
foreach (var addressBcc in message.GetSection("AddressesBcc").GetChildren())
emailConfig.Message.AddressesBcc.Add(StringUtil.TrimAll(addressBcc.Value));
IConfigurationSection smtp = emailConfigSection.GetSection("SMTP");
emailConfig.SMTP.Server = smtp["Server"];
emailConfig.SMTP.ServerPort = StringUtil.ToInt(smtp["ServerPort"]);
emailConfig.SMTP.UseSSL = StringUtil.ToBool(smtp["UseSSL"]);
IConfigurationSection send = emailConfigSection.GetSection("Send");
emailConfig.Send.Using = send["Using"];
emailConfig.Send.UserName = send["UserName"];
emailConfig.Send.Password = send["Password"];
}
return emailConfig;
}
//-------------------------------------------------------------------
// Send email.
public static bool SendEmail(SaEmailData emailData)
{
// ToDo return EmailConsole.SendEmail(emailData);
throw new NotSupportedException();
}
//-------------------------------------------------------------------
/// <summary>
/// Replace dynamic tags in email message body.
/// <para></para>
/// </summary>
public static void ReplaceDynamicEmailBodyTags(SaEmailData emailData, SaxSessionData sessionData, SaxUserAccount userAccount)
{
// Replace core tags, eg. main stylesheet.
// ToDo. EmailConsole.ReplaceDynamicEmailTags(emailData);
// Replace tags known to the web application.
}
//-------------------------------------------------------------------
}
//=======================================================================
public static class EmailConfigList
{
public static SaxEmailConfig getConfigForSession(this List<SaxEmailConfig> emailConfigs, SaxSessionConfig sessionConfig)
{
if (sessionConfig != null && !StringUtil.IsEmpty(sessionConfig.Email.Context))
{
foreach (SaxEmailConfig emailConfig in emailConfigs)
{
if (emailConfig.Context.EqualsCI(sessionConfig.Email.Context))
return emailConfig;
}
}
return new SaxEmailConfig("");
}
}
//=======================================================================
}
@@ -0,0 +1,71 @@
using Microsoft.Extensions.Configuration;
using Sarand.Net.Standard.Base.Core;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Core.Web.Base.App;
using System;
using System.Collections.Generic;
using System.Text;
using Sarand.Net.Standard.Base.Utils;
namespace Saxonite.Net.Core.Web.Base.Helpers
{
//=======================================================================
public static class SaxSessionConfigHelper
{
//-------------------------------------------------------------------
public static Dictionary<String, SaxSessionConfig> ExtractSessionConfigs(IConfigurationSection sessionConfigsSection)
{
// NB The set of configurations will only allow one per context.
// Only the last configuration found for each context will be stored (NB usually only one of each apart from 'Defaults').
Dictionary<String, SaxSessionConfig> sessionConfigs = new Dictionary<String, SaxSessionConfig>();
// Start the list with a default 'Defaults' configuration - values set to application defaults (defined in Sax).
// NB This can be overridden in each level of configuration.
// Settings from the 'Defaults' configuration provide values where they are missing from other configurations.
// NB Coverage may not be 100% - just where it's proven needed.
SaxSessionConfig defaultsSessionConfig = new SaxSessionConfig(Sax.Config.Context_Defaults, null, null);
sessionConfigs.Add(defaultsSessionConfig.Context, defaultsSessionConfig);
// Load session configurations.
foreach (ConfigurationSection sessionConfigSection in sessionConfigsSection.GetChildren())
{
if (StringUtil.IsEmpty(sessionConfigSection.Key)) continue;
// Extract the configuration, supplying current 'Defaults' to supply missing values, and add to the list.
SaxSessionConfig sessionConfig = new SaxSessionConfig(sessionConfigSection.Key, sessionConfigSection, defaultsSessionConfig);
sessionConfigs.Remove(sessionConfigSection.Key);
sessionConfigs.Add(sessionConfigSection.Key, sessionConfig);
// Replace the 'Defaults' configuration if this one matches the context.
if (sessionConfig.Context.EqualsCI(Sax.Config.Context_Defaults))
defaultsSessionConfig = sessionConfig;
}
return sessionConfigs;
}
//-------------------------------------------------------------------
}
//=======================================================================
public static class SessionConfigList
{
public static SaxSessionConfig getConfigForApplication(this List<SaxSessionConfig> sessionConfigs)
{
SaxSessionConfig defaultsSessionConfig = new SaxSessionConfig("", null, null);
SaxApplication_Base app = SaxGlobals.App;
if (app != null)
{
foreach (SaxSessionConfig sessionConfig in sessionConfigs)
{
if (sessionConfig.Environments.ContainsCI(app.EnvironmentName))
return sessionConfig;
if (sessionConfig.Context.EqualsCI(Sax.Config.Context_Defaults))
defaultsSessionConfig = sessionConfig;
}
}
return defaultsSessionConfig;
}
}
//=======================================================================
}
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Middleware
{
//=======================================================================
/// <summary>
/// Force blank form values to be populated to the model as empty strings rather than null values.
/// </summary>
public class SaxMetadataProvider : IMetadataDetailsProvider, IDisplayMetadataProvider
{
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
if (context.Key.MetadataKind == ModelMetadataKind.Property)
{
context.DisplayMetadata.ConvertEmptyStringToNull = false;
}
}
}
//=======================================================================
}
@@ -0,0 +1,102 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Saxonite.Net.Core.Web.Base.Middleware
{
//=======================================================================
/// <summary>
/// Maps a browser request to a data object. Default class provides a base implementation of a model binder.
/// <para>Overridden to trim whitespace from input values.</para>
/// </summary>
public class SaxModelBinder : ComplexTypeModelBinder
{
//-------------------------------------------------------------------
// Construction.
public SaxModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) :
base(propertyBinders, loggerFactory)
{
}
//-------------------------------------------------------------------
/// <summary>
/// Creates the model with one (or more) injected service parameters.
/// </summary>
protected override object CreateModel(ModelBindingContext bindingContext)
{
var services = bindingContext.HttpContext.RequestServices;
var modelType = bindingContext.ModelType;
var ctors = modelType.GetConstructors();
foreach (var ctor in ctors)
{
var paramTypes = ctor.GetParameters().Select(p => p.ParameterType).ToList();
var parameters = paramTypes.Select(p => services.GetService(p)).ToArray();
if (parameters.All(p => p != null))
{
var model = ctor.Invoke(parameters);
return model;
}
}
return null;
}
//-------------------------------------------------------------------
/// <summary>
/// Intercept String properties and trim whitespace.
/// </summary>
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
{
string fn = GetType().Name + ".SetProperty";
//if (SaxSystem.DebugMode) Logger.Debug(fn + " Form property " + modelName + ": '" + result.Model.ToString() + ".");
object originalValue = result.Model;
// Exclude properties that retain leading/trailing whitespace.
if (result.Model is string)
{
if (!modelName.ContainsCI("Password"))
{
string value = result.Model as string;
// Trim leading and trailing whitespace.
value = StringUtil.TrimAll(value);
// Check for multi-line.
if (value.Contains('\n'))
{
// Simplify line delimiters.
value = Regex.Replace(value, "\\r\\n", "\n");
// Remove white space in front of newlines.
value = Regex.Replace(value, "\\s+\\n", "\n");
// Remove multiple newlines.
value = Regex.Replace(value, "\\n+", "\n");
}
// Email addresses must be lowercase to ensure match to encrypted data.
if (modelName.EqualsCI("EmailAddress") || modelName.EqualsCI("AccountEmailAddress"))
value = value.ToLower();
// Uppercase selected values.
if (modelName.EqualsCI("Postcode") || modelName.EqualsCI("AccountPostcode") || modelName.EqualsCI("RegisteredAddressPostcode"))
value = value.ToUpper();
// Assign resulting value.
result = ModelBindingResult.Success(value);
}
}
if (result.Model != null && SaxSystem.DebugMode)
SaLogger.Debug(fn + " Form property " + modelName + ": '" + result.Model.ToString() + "'." + (originalValue != null && !originalValue.Equals(result.Model) ? " Original value: '" + originalValue.ToString() + "'" : ""));
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Saxonite.Net.Core.Web.Base.Middleware
{
//=======================================================================
/// <summary>
/// Maps a browser request to a data object. Default class provides a base implementation of a model binder.
/// <para>Overridden to trim whitespace from input values.</para>
/// </summary>
public class SaxModelBinderProvider : IModelBinderProvider
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SaxModelBinderProvider()
{
}
//-------------------------------------------------------------------
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder);
ILoggerFactory loggerFactory = (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
return new SaxModelBinder(propertyBinders, loggerFactory);
}
// Default - return null so that default binding provider is used.
return null;
}
}
//=======================================================================
}
@@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Base.Middleware
{
//=======================================================================
/// <summary>
/// Extend IApplicationBuilder class to use the anit-forgery token validation middleware.
/// </summary>
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
{
return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
}
}
//=======================================================================
/// <summary>
/// Ensure relevant actions validate the anti-forgery token, eg. post requests.
/// </summary>
public class ValidateAntiForgeryTokenMiddleware
{
private readonly RequestDelegate _next;
private readonly IAntiforgery _antiforgery;
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
{
_next = next;
_antiforgery = antiforgery;
}
public async Task Invoke(HttpContext context)
{
String fn = GetType().Name + ".Invoke";
// Log action.
//String rawTarget = context.Features.Get<IHttpRequestFeature>().RawTarget;
if (SaxSystem.DebugMode) SaLogger.Debug(fn + " Action: '" + Regex.Replace(context.Request.Path.ToString(), "^/", "") + "'. Method: " + context.Request.Method);
// Intercept POST requests and apply anti-forgery test.
if (HttpMethods.IsPost(context.Request.Method))
{
await _antiforgery.ValidateRequestAsync(context);
}
try
{
await _next(context);
}
catch (Exception exc)
{
SaLogger.Warn(fn + " " + ExceptionUtil.GetLogMessage(exc));
throw exc;
}
}
}
//=======================================================================
}
@@ -0,0 +1,275 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
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.Base.App;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Base.Mgr;
using System;
using System.Collections.Generic;
namespace Saxonite.Net.Core.Web.Base.Models
{
//=======================================================================
public class SaxModel_Base
{
//-------------------------------------------------------------------
// Data members.
private SaxInternals internals;
private readonly List<String> infoMessages = new List<string>();
private readonly List<String> warningMessages = new List<string>();
private readonly List<String> errorMessages = new List<string>();
//-------------------------------------------------------------------
// Protected construction.
protected SaxModel_Base(SaxLocaliser localiser)
{
Localiser = localiser;
}
//-------------------------------------------------------------------
// Application properties.
private SaxApplication_Web App { get { return SaxApplication_Web.WebApp; } }
protected SaxSessionConfig SessionConfig { get { return App.SessionConfig; } }
protected SaxSystem_Web System { get { return App.System_Web; } }
public String SessionCookieName { get { return App.SessionCookieName; } }
public SaxSessionData SessionData { get; set; }
public SaxSessionContext SessionContext { get { return SessionData?.SessionContext ?? App.SessionContext; } }
public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } }
public bool DebugMode { get { return SaxSystem.DebugMode; } }
public SaxLocaliser Localiser { get; }
//-------------------------------------------------------------------
// Business components.
protected SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
// Consoles.
protected SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
// Managers.
protected SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
protected SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
protected SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
protected SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
// System codes.
public List<TypeCodeDesc> RegionTypes { get { return Internals.RegionTypes; } }
public List<TypeCodeDesc> UserRoles { get { return Internals.UserRoles; } }
public List<TypeCodeDesc> OrganisationTypes { get { return Internals.OrganisationTypes; } }
public List<TypeCodeDesc> OrganisationCategories { get { return Internals.OrganisationCategories; } }
public List<TypeCodeDesc> OrganisationRoles { get { return Internals.OrganisationRoles; } }
//-------------------------------------------------------------------
// Page configuration.
public bool OK { get { return !HasMessages(); } }
public virtual bool ProhibitCache { get; set; } = true;
public String PageCaption { get; set; }
public String InstanceVersion { get { return !StringUtil.IsEmpty(App.SessionConfig.Instance) ? App.SessionConfig.Instance + " v" + Sax.Version.GetFullVersion() : "v" + Sax.Version.GetAppVersion(); } }
public bool IncludeGoogleAnalytics { get; set; } = false;
//-------------------------------------------------------------------
// Security.
public bool ValidUserSession { get { return SessionData?.ValidUserSession ?? false; } }
public bool AwaitingLogin { get { return !ValidUserSession; } }
public bool KeepLoggedIn { get { return !AwaitingLogin && SessionData.User.KeepLoggedIn; } }
/// <summary>Maximum length allowed for password entry: sign-up, change password etc.</summary>
public int PasswordMaxLength { get; } = 32;
// Account actions.
// NB The form proerty will never be used - account actions processed within page.
public String AccountAction { get; set; }
public IEnumerable<SelectListItem> GetAccountActions()
{
List<CodeDesc> items = new List<CodeDesc>();
if (SessionData?.ValidUserSession ?? false)
{
items.Add("Profile", Localiser["label.view.profile"]);
items.Add("ChangePassword", Localiser["label.change.password"]);
items.Add("SignOut", Localiser["label.sign.out"]);
}
return new SelectList(items, "Code", "Desc");
}
//-------------------------------------------------------------------
/// <summary>
/// Form control value max length.
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
/// </summary>
public int GetControlMaxLength(SaxDbDfn.ColumnIdx column)
{
int maxLength = (int)SaxDbDfn.GetColTextLen(column);
switch (column)
{
case SaxDbDfn.ColumnIdx.UserAccount_EmailAddress:
return Math.Min(maxLength, 60);
case SaxDbDfn.ColumnIdx.UserAccount_FirstName:
case SaxDbDfn.ColumnIdx.UserAccount_LastName:
return Math.Min(maxLength, 40);
case SaxDbDfn.ColumnIdx.Organisation_Name:
return Math.Min(maxLength, 80);
case SaxDbDfn.ColumnIdx.UserAccount_PhoneMain:
case SaxDbDfn.ColumnIdx.UserAccount_PhoneMobile:
case SaxDbDfn.ColumnIdx.Organisation_PhoneMain:
case SaxDbDfn.ColumnIdx.Organisation_PhoneAlt:
return Math.Min(maxLength, 20);
//case SaxDbDfn.ColumnIdx.UserAccount_Postcode:
//case SaxDbDfn.ColumnIdx.ItemAddress_Postcode:
// return Math.Min(maxLength, 16);
}
return maxLength;
}
//-------------------------------------------------------------------
// Copyright notice.
public HtmlString CopyrightNotice
{
get
{
int initialYear = 2020;
int currentYear = DateTime.Today.Year;
return Localiser.GetHtmlText("copyright.symbol.year.html", (initialYear != currentYear ? initialYear + "-" : "") + currentYear.ToString());
}
}
//-------------------------------------------------------------------
// Link URLs.
public class LinksData
{
public String Facebook { get; } = "https://www.facebook.com/saxonite/";
public String LinkedIn { get; } = "https://www.linkedin.com/company/saxonite/about/";
}
public LinksData Links { get; } = new LinksData();
//-------------------------------------------------------------------
// Form data.
/// <summary>List of Yes/No options for dropdown.</summary>
public IEnumerable<SelectListItem> YesNoOptionsForSelect
{
get { return new SelectList(SaxSystem.GetCodesOfType(Sax.CodeType.YesNo), "Code", "Desc"); }
}
/// <summary>List of titles for dropdown.</summary>
public IEnumerable<SelectListItem> TitlesForSelect
{
get { return new SelectList(SaxSystem.GetCodesOfType(Sax.CodeType.Title), "Code", "Desc"); }
}
/// <summary>List of Region Types for dropdown.</summary>
public IEnumerable<SelectListItem> RegionTypesForSelect
{
get { return new SelectList(RegionTypes, "Code", "Desc"); }
}
/// <summary>List of Organisation Types for dropdown.</summary>
public IEnumerable<SelectListItem> OrganisationTypesForSelect
{
get { return new SelectList(OrganisationTypes, "Code", "Desc"); }
}
/// <summary>List of Organisation Categories for dropdown.</summary>
public IEnumerable<SelectListItem> OrganisationCategoriesForSelect
{
get { return new SelectList(OrganisationCategories, "Code", "Desc"); }
}
//-------------------------------------------------------------------
// Version parameter for includes.
public String HrefVersion
{
get
{
DateTime versionDate = SaxSystem.AssemblyBuildAt;
if (!DateUtil.IsValid(versionDate) || SaxSystem.DebugMode)
versionDate = DateTime.Now;
return DateUtil.Format(versionDate, "yyyyMMdd" /*+ (SacsSystem.Debug ? "HHmm" : "")*/);
}
}
// Additional includes.
public List<String> AdditionalFiles_CSS { get; set; } = new List<string>();
public List<String> AdditionalFiles_JS { get; set; } = new List<string>();
//-------------------------------------------------------------------
// Redirection locations.
public class RedirectLocationData
{
// Properties populated in Controller_Base.OnActionExecuted.
public String Base { get; internal set; }
public String Home { get; internal set; }
public String Home_SessionExpired { get; internal set; }
}
public RedirectLocationData RedirectLocation { get; } = new RedirectLocationData();
//-------------------------------------------------------------------
// Paging properties.
private class DataPaging
{
private DbQuery.Paging paging { get; } = new DbQuery.Paging();
public void PopulatePagingTo(DbQuery.Paging resultsPaging)
{
resultsPaging.PageLength = paging.PageLength;
resultsPaging.PageNo = paging.PageNo;
}
public bool IsPaged { get { return paging.Paged; } }
public uint PageNo { get { return paging.PageNo; } set { paging.PageNo = 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 ushort ItemCount { get; set; }
public ulong ItemTotal { get; set; }
}
private DataPaging Paging { get; } = new DataPaging();
public bool DataPaged { get { return Paging.IsPaged; } }
public uint DataPageNo { get { return Paging.PageNo; } set { Paging.PageNo = value; } }
public ushort DataPageLength { get { return Paging.PageLength; } set { Paging.PageLength = value; } }
public uint DataPageCount { get { return (DataPageLength > 0 ? (uint)Math.Ceiling((decimal)DataItemTotal / DataPageLength) : 0); } }
public ushort DataItemCount { get; set; }
public ulong DataItemTotal { get; set; }
public void PopulatePagingTo(DbQuery.Paging resultsPaging)
{
Paging.PopulatePagingTo(resultsPaging);
}
//-------------------------------------------------------------------
// Form properties.
/// <summary>
/// User session index to identify the site resources, eg. database.
/// <para>Equivalent to the Site index on the login form.</para>
/// <para>Populates value on logout form to identify default site for next login.</para>
/// </summary>
public ushort SessionIdx { get { return SessionContext.Index; } }
public String FormAction { get; set; }
//-------------------------------------------------------------------
// Messages.
//-------------------------------------------------------------------
public bool HasMessages()
{
return infoMessages.Count > 0 || warningMessages.Count > 0 || errorMessages.Count > 0;
}
public List<String> InfoMessages { get { return new List<string>(infoMessages); } }
public List<String> WarningMessages { get { return new List<string>(warningMessages); } }
public List<String> ErrorMessages { get { return new List<string>(errorMessages); } }
//-------------------------------------------------------------------
// Form messages.
public void AddInfoMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
infoMessages.Add(message);
}
public void AddWarningMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
warningMessages.Add(message);
}
public void AddErrorMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
errorMessages.Add(message);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
+28
View File
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Saxonite.Net.Core.Web</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
<ProjectReference Include="..\SaxoniteStandardBase\SaxoniteStandardBase.csproj" />
<ProjectReference Include="..\SaxoniteStandardDbas\SaxoniteStandardDbas.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Base\Core\" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.13" />
</ItemGroup>
</Project>
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "5.0.5",
"commands": [
"dotnet-ef"
]
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.App
{
//=======================================================================
public class Program
{
//-------------------------------------------------------------------
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
//-------------------------------------------------------------------
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<SaxStartup_Admin>();
});
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,232 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
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.Email;
using Sarand.Net.Standard.Base.IO;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Dbas.Base;
using Saxonite.Net.Core.Web.Admin.Consoles;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Helpers;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Base.Helpers;
using Saxonite.Net.Standard.Base.Mgr;
using Saxonite.Net.Standard.Dbas.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.App
{
//=======================================================================
public class SaxApplication_Admin : SaxApplication_Web
{
//-------------------------------------------------------------------
// Data members.
private bool generateDatabaseDefinitions = false;
private bool populateRegions = false;
private Dictionary<String, SaxSessionConfig> sessionConfigs = new Dictionary<string, SaxSessionConfig>();
private Dictionary<String, SaxEmailConfig> emailConfigs = new Dictionary<string, SaxEmailConfig>();
// Session contexts.
private class SessionContextData
{
public SessionContextData(SaxSessionContext_Web sessionContext)
{
SessionContext = sessionContext;
}
public SaxSessionContext_Web SessionContext { get; }
public ISacsDatabase CoreDatabase { get; set; }
}
private readonly Dictionary<int, SessionContextData> sessionContexts = new Dictionary<int, SessionContextData>();
//-------------------------------------------------------------------
// Construction.
public SaxApplication_Admin(IConfiguration configuration)
{
InitialiseTemporaryLoggingFolder();
try
{
AppInstance = this;
SaxGlobals.App = this;
SaxGlobals.System = (System_Web = new SaxSystem_Web());
Configuration = configuration;
ConsoleHelper_Web = new SaxConsoleHelper_WebAdmin(this);
}
catch (Exception exc)
{
SaLogger.Error(GetType().Name + ".CTR " + ExceptionUtil.GetErrorStateMessage(exc));
throw exc;
}
}
//-------------------------------------------------------------------
// Properties.
public static SaxApplication_Admin AppInstance { get; private set; }
public override ISacsDatabase GetCoreDatabase(SaxSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.CoreDatabase;
}
public override String SessionCookieName { get { return Sax.Config.Admin.Session.Cookie.Name; } }
// Configuration.
public override SaxSessionConfig SessionConfig { get { return sessionConfigs.Values.ToList().getConfigForApplication(); } }
public override SaxEmailConfig EmailConfig { get { return emailConfigs.Values.ToList().getConfigForSession(SessionConfig); } }
//-------------------------------------------------------------------
// Initialisation.
/// <summary>Initialise environment - from Startup.</summary>
internal void InitWebHostEnvironment(IWebHostEnvironment env)
{
InitEnvironment(env);
}
/// <summary>Initialise environment - from internal (above) or unit tests.</summary>
protected void InitEnvironment(IWebHostEnvironment env)
{
String fn = GetType().Name + ".InitEnvironment";
Development = env.IsDevelopment();
environmentName = env.EnvironmentName;
SaLogger.LogFilenamePrefix = "Sax-Web-Adm";
SaLogger.IncludeHostInFileMessage = false;
SaLogger.IncludeUserInFileMessage = false;
try
{
// Load server configuration.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.machine.{SaxSystem.ComputerName}.json", optional: false, reloadOnChange: false)
.AddEnvironmentVariables();
SaxConfiguration = builder.Build();
// Application configuration.
sessionConfigs = SaxSessionConfigHelper.ExtractSessionConfigs(SaxConfiguration.GetSection("SessionConfigs"));
emailConfigs = SaxEmailConfigHelper.ExtractEmailConfigs(SaxConfiguration.GetSection("EmailConfigs"));
LoggingRootFolder = SessionConfig.Logging.RootFolder;
// Application instance.
if (SessionConfig.Instance.EqualsCI("Dev")) instanceType = Sax.InstanceType.Dev;
else if (SessionConfig.Instance.ContainsCI("Q")) instanceType = Sax.InstanceType.QC;
else if (SessionConfig.Instance.ContainsCI("UAT")) instanceType = Sax.InstanceType.UAT;
else if (SessionConfig.Instance.EqualsCI("PreProd")) instanceType = Sax.InstanceType.UAT;
// Initialise hard-coded logging path.
if (StringUtil.IsEmpty(LoggingRootFolder))
{
if (SaxSystem.ComputerName.EqualsCI("WIN-BPCLM1QUOMT"))
LoggingRootFolder = @"D:\Saxonite\Logs\" + (Development ? "Dev" : "Test");
}
if (DirectoryUtil.DirectoryExists(LoggingRootFolder))
SaLogger.LogFolder = LoggingRootFolder;
LogApplicationStart(fn);
// Configure database connection.
SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig();
config.location = SessionConfig.CoreDatabase.Location;
config.user = SessionConfig.CoreDatabase.User;
config.pword = SessionConfig.CoreDatabase.Password;
SacsDatabase coreDatabase = SacsDatabaseConsole.OpenOdbcDatabase(config);
if (coreDatabase.HasErrorState())
SaLogger.Warn(fn + " Database initialisation error: " + coreDatabase.ErrorStateMessage);
// Session context - only one context available/necessary (for now).
SessionContextData sessionContextData = new SessionContextData(new SaxSessionContext_Web(100, Sax.Application.Admin));
sessionContextData.CoreDatabase = coreDatabase;
sessionContexts.Add(sessionContextData.SessionContext.Index, sessionContextData);
SessionContext = sessionContextData.SessionContext;
}
catch (Exception exc)
{
SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc));
}
// Occasional startup processing.
if (generateDatabaseDefinitions)
GenerateDatabaseDefinition(SessionContext);
if (populateRegions)
populateRegionsFromDataFile(SessionContext);
}
//-------------------------------------------------------------------
private void GenerateDatabaseDefinition(SaxSessionContext sessionContext)
{
if (!generateDatabaseDefinitions || !SaxSystem.DebugMode || SessionContext == null) return;
if (!StringUtil.EmptyOnNull(EnvironmentName).EqualsCI("Development")) return;
ISacsDatabase database = GetCoreDatabase(sessionContext);
if (database == null || StringUtil.IsEmpty(database.ConnectionString)) return;
// Generate database definitions.
SaxDbDfnGenerator.Generate(database.ConnectionString);
//SaUpdateLogDbDfnGenerator.Generate(database.ConnectionString);
}
//-------------------------------------------------------------------
private void populateRegionsFromDataFile(SaxSessionContext sessionContext)
{
using (SaxInternals internals = new SaxInternals(sessionContext))
{
SaxReferenceMgr referenceMgr = internals.ReferenceMgr;
String regionsData = SaxResourceHelper.GetTextResource(SaxResourceHelper.TextResource.Data_CountriesWithRegions_Txt);
String[] regionDataValues = regionsData.Split("\n");
SysId parentRegionId = null;
for (int idx = 0; idx < regionDataValues.Length; idx++)
{
String regionData = regionDataValues[idx].ReplaceAll("\r", "");
if (StringUtil.IsEmpty(regionData)) continue;
if (regionData.Length > 3 && regionData[2] == ':')
{
String iso2Code = regionData.Substring(0, 2);
String name = regionData.Substring(3);
SaxRegion region = getRegionByName(referenceMgr, name);
if (region == null)
{
region = referenceMgr.CreateRegion();
region.RegionType = Sax.RegionType.Country;
region.CountryIso2Code = iso2Code;
region.Name = name;
region.RegionIdParent = parentRegionId;
if (!referenceMgr.InsertRegion(region, new DbUpdateInfo(new SysId(), "", "")))
{
SaLogger.Error("Failed to insert region - " + referenceMgr.ErrorStateMessage);
return;
}
}
}
else
{
// Sub continent.
SaxRegion region = getRegionByName(referenceMgr, regionData);
if (region == null)
{
region = referenceMgr.CreateRegion();
region.RegionType = Sax.RegionType.SubContinent;
region.Name = regionData;
if (!referenceMgr.InsertRegion(region, new DbUpdateInfo(new SysId(), "", "")))
{
SaLogger.Error("Failed to insert region - " + referenceMgr.ErrorStateMessage);
return;
}
}
parentRegionId = region.Id;
}
}
}
}
private SaxRegion getRegionByName(SaxReferenceMgr referenceMgr, String name)
{
SaxRegion.Params prmRegion = new SaxRegion.Params();
prmRegion.NameFull = name;
return referenceMgr.GetRegion(prmRegion);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,47 @@
using Microsoft.Extensions.Localization;
using Saxonite.Net.Core.Web.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web
{
// <summary>
/// Dummy classes to group shared resources - named as per '.resx' file in root namespace.
/// <para>Required to create the IStringLocalizer instance using the type from the class.</para>
/// <para>Name of the class is used as the prefix for localisation filenames.</para>
/// <para>Resource files are in the Localization ResourcesPath in Startup.cs.</para>
/// </summary>
public class SaxAdminProperties { }
// Class must be in Root/Default Namespace to reference properties with same name.
// NB 'Assembly name' must be the same as 'Default Namespace' until 'Root Namespace' can be set (.Net Core 2.1?).
// Ref. Project Properties - Application.
}
namespace Saxonite.Net.Core.Web.Admin.App
{
//=======================================================================
public class SaxLocaliser_Admin : SaxLocaliser
{
//-------------------------------------------------------------------
// Data members.
private readonly IStringLocalizer<SaxAdminProperties> adminProperties;
//-------------------------------------------------------------------
/// <summary>
/// Localisers are supplied using construction injection.
/// </summary>
public SaxLocaliser_Admin(IStringLocalizer<SaxAdminProperties> webAppProperties)
{
this.adminProperties = webAppProperties;
}
//-------------------------------------------------------------------
// Properties.
public override IStringLocalizer MainProperties { get { return adminProperties; } }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,37 @@
using Newtonsoft.Json;
using Saxonite.Net.Core.Web.Base.App;
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.App
{
//=======================================================================
public class SaxSessionData_Admin : SaxSessionData_Web
{
//-------------------------------------------------------------------
// Construction.
[JsonConstructor]
private SaxSessionData_Admin(SaxSessionContext sessionContext) : this(sessionContext, null, null) { }
/// <summary>
/// SessionData construction.
/// <para>SessionContext and UserAccount must be supplied.</para>
/// <para>Organisation is optional.</para>
/// </summary>
public SaxSessionData_Admin(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles) :
base(sessionContext, userAccount, userRoles)
{
}
//-------------------------------------------------------------------
// Properties.
/// <summary>User - JSON deserialisation route.</summary>
public new UserData User { get { return base.User; } set { SetUser(value); } }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.App
{
//=======================================================================
public class SaxStartup_Admin : SaxStartup
{
//-------------------------------------------------------------------
// Data members.
private SaxApplication_Admin application = null;
//-------------------------------------------------------------------
// Construction.
public SaxStartup_Admin(IConfiguration configuration) : base(configuration)
{
// Initialise application.
application = new SaxApplication_Admin(Configuration);
}
//-------------------------------------------------------------------
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Common configuration.
ConfigureServicesConfig configureServicesConfig = new ConfigureServicesConfig();
configureServicesConfig.services = services;
configureServicesConfig.sessionCookieName = Sax.Config.Admin.Session.Cookie.Name;
configureServicesConfig.sessionCookieHttpOnly = Sax.Config.Admin.Session.Cookie.HttpOnly;
configureServicesConfig.sessionIdleTimeout = Sax.Config.Admin.Session.Cookie.IdleTimeout;
ConfigureServices(configureServicesConfig);
// Application localisation.
services.AddSingleton<SaxLocaliser, SaxLocaliser_Admin>();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
}
//-------------------------------------------------------------------
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostApplicationLifetime hostApplicationLifetime, IWebHostEnvironment env)
{
application.InitWebHostEnvironment(env);
// Common configuration.
ConfigureConfig configureConfig = new ConfigureConfig();
configureConfig.app = app;
configureConfig.hostApplicationLifetime = hostApplicationLifetime;
configureConfig.env = env;
Configure(configureConfig);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,45 @@
using Saxonite.Net.Core.Web.Admin.App;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Consoles
{
//=======================================================================
public class SaxConsoleHelper_WebAdmin : SaxConsoleHelper_Web
{
//-------------------------------------------------------------------
// Data members:
private readonly SaxApplication_Admin app;
//-------------------------------------------------------------------
// Construction.
public SaxConsoleHelper_WebAdmin(SaxApplication_Admin app)
{
this.app = app;
}
//-------------------------------------------------------------------
// Data cache console.
public override SaxDataCacheConsole GetDataCacheConsole(SaxSessionContext sessionContext)
{
String fn = GetType().Name + ".GetDataCacheConsole";
throw new NotSupportedException(fn + " Data Cache Console not supported in Admin app.");
}
//-------------------------------------------------------------------
// Email console.
public override SaxEmailConsole CreateEmailConsole(SaxSessionContext sessionContext)
{
SaxEmailConsole emailConsole = new SaxEmailConsole_Web(app.EmailConfig);
return emailConsole;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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.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 class HomeController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public HomeController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Index()
{
String fn = GetType().Name + ".Index";
// Check for 'Keep Logged In' cookie.
// NB Only allowed if there isn't a current user session.
if (!ValidUserSession && SessionConfig.Security.SignIn.KeepLoggedIn.Allowed)
{
String encryptedParams = GetRequestCookie(CookieName_KeepLoggedIn);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (SysId.IsValid(keepLoggedInParams.UserAccountId))
{
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId);
if (userAccount != null)
{
if (userAccount.IsStatusActive)
{
String errorMessage = "";
if (SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), true, ref errorMessage))
SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix);
else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount is not active." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount not found." + logSuffix);
}
}
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
return RedirectToAction("", "Security");
}
// Home page.
Model_Home model = new Model_Home(Localiser);
model.ProhibitCache = false;
return View(model);
}
//-------------------------------------------------------------------
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,252 @@
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
{
//=======================================================================
// Controller: Organisation - Ajax
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Organisation data.
public class OrganisationData_List
{
public String Id { get; set; }
public String Name { get; set; }
public String TypeCode { get; set; }
public String TypeDesc { get; set; }
public String CategoryCode { get; set; }
public String CategoryDesc { get; set; }
public long MemberCount { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public class OrganisationData_Full : OrganisationData_List
{
}
private void PopulateOrganisationResponseData(SaxOrganisation organisation, OrganisationData_List data)
{
data.Id = SysId.ToString(organisation.Id);
data.Name = organisation.Name;
data.TypeCode = OrganisationTypes.GetCodeForType(organisation.OrganisationType);
data.TypeDesc = OrganisationTypes.GetDescForType(organisation.OrganisationType);
data.CategoryCode = OrganisationCategories.GetCodeForType(organisation.OrganisationCategory);
data.CategoryDesc = OrganisationCategories.GetDescForType(organisation.OrganisationCategory);
data.MemberCount = AccountMgr.GetOrganisationMemberCount(organisation.Id);
data.StatusCode = organisation.StatusCode;
data.StatusDesc = organisation.StatusDesc;
}
private void PopulateOrganisationResponseData(SaxOrganisation organisation, OrganisationData_Full data)
{
PopulateOrganisationResponseData(organisation, data as OrganisationData_List);
}
//-------------------------------------------------------------------
// Get Organisation List ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_OrganisationList : AjaxResponse_Data
{
public List<OrganisationData_List> Organisations { get; } = new List<OrganisationData_List>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetList")]
public IActionResult Ajax_GetList(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_GetList";
String messageTitle = Localiser["organisation.caption.organisations"];
AjaxResponse_OrganisationList responseData = new AjaxResponse_OrganisationList();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Item filter.
SaxOrganisation.Params prmOrganisation = new SaxOrganisation.Params();
prmOrganisation.OrganisationFilter = model.Filter.Organisation;
prmOrganisation.StatusCat = SaxOrganisation.StatusCat.NotDeleted;
model.DataItemTotal = (ulong)AccountMgr.GetOrganisationCount(prmOrganisation);
model.PopulatePagingTo(prmOrganisation.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Id);
ColumnDfn colName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name);
ColumnDfn colOrganisationType = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationType);
ColumnDfn colOrganisationCategory = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationCategory);
ColumnDfn colPhoneMain = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.PhoneMain);
ColumnDfn colPhoneAlt = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.PhoneAlt);
ColumnDfn colUserCount = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.User_Count);
ColumnDfn colMemberCount = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Member_Count);
ColumnDfn colStatus = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colName,
colOrganisationType,
colOrganisationCategory,
colPhoneMain,
colPhoneAlt,
colUserCount,
colMemberCount,
colStatus
};
// Query items.
model.DataItemCount = 0;
using (DbResultSet resultSet = AccountMgr.ExecuteOrganisationQuery(columns, prmOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
model.DataItemCount++;
NamedValue.Map values = resultSet.GetRecordValues();
OrganisationData_List organisationData = new OrganisationData_List()
{
Id = values.GetString(colId.DataName),
Name = values.GetString(colName.DataName),
TypeCode = values.GetString(colOrganisationType.DataName),
TypeDesc = OrganisationTypes.GetDescForCode(values.GetString(colOrganisationType.DataName)),
CategoryCode = values.GetString(colOrganisationCategory.DataName),
CategoryDesc = OrganisationCategories.GetDescForCode(values.GetString(colOrganisationCategory.DataName)),
MemberCount = StringUtil.ToLong(values.GetString(colMemberCount.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Organisations.Add(organisationData);
}
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Organisation ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_Organisation : AjaxResponse_Data
{
public OrganisationData_Full organisation { get; } = new OrganisationData_Full();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetItem")]
public IActionResult Ajax_GetItem(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_GetItem";
String messageTitle = Localiser["organisation.caption.organisation"];
AjaxResponse_Organisation responseData = new AjaxResponse_Organisation();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Identify item.
SysId organisationId = new SysId(StringUtil.ToLong(model.OrganisationId));
if (!SysId.IsValid(organisationId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
if (organisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve organisation for Id: '" + model.OrganisationId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateOrganisationResponseData(organisation, responseData.organisation);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Organisation ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/StoreItem")]
public IActionResult Ajax_StoreItem(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_StoreItem";
String messageTitle = Localiser["organisation.caption.organisation"];
AjaxResponse_Organisation responseData = new AjaxResponse_Organisation();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Validate item properties.
model.ValidateOrganisationData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve existing item.
SysId organisationId = new SysId(StringUtil.ToLong(model.OrganisationId));
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
if (organisation == null && SysId.IsValid(organisationId))
{
SaLogger.Warn(fn + " Failed to retrieve organisation for Id: '" + model.OrganisationId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Create new item if required.
if (organisation == null)
organisation = AccountMgr.CreateOrganisation();
// Populate item properties.
if (model.OrganisationName != null)
organisation.Name = model.OrganisationName;
if (model.OrganisationTypeValue != null)
organisation.OrganisationType = model.OrganisationType;
if (model.OrganisationCategoryValue != null)
organisation.OrganisationCategory = model.OrganisationCategory;
if (model.OrganisationStatusCode != null)
organisation.Status = model.OrganisationStatus;
// Store item.
if (!SysId.IsValid(organisation.Id))
{
if (!AccountMgr.InsertOrganisation(organisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert organisation: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
else
{
if (!AccountMgr.UpdateOrganisation(organisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update organisation [Id:" + SysId.ToString(organisation.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateOrganisationResponseData(organisation, responseData.organisation);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,326 @@
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
{
//=======================================================================
// Controller: Organisation - Members - Ajax
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Member data.
public class MemberData_List
{
public String Id { get; set; }
public String UserAccountId { get; set; }
public String EmailAddress { get; set; }
public String DisplayName { get; set; }
public String OrganisationId { get; set; }
public String OrganisationName { get; set; }
public List<String> RoleCodes { get; } = new List<string>();
public int RoleCount { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public class MemberData_Full : MemberData_List
{
}
private void PopulateMemberResponseData(MemberData_List data, SaxUserOrganisation userOrganisation, SaxUserAccount userAccount, SaxOrganisation organisation)
{
data.Id = SysId.ToString(userOrganisation.Id);
data.UserAccountId = SysId.ToString(userAccount.Id);
data.EmailAddress = userAccount.EmailAddress;
data.DisplayName = userAccount.GetDisplayName();
data.OrganisationId = SysId.ToString(organisation.Id);
data.OrganisationName = organisation.Name;
// Exclude 'Member' role.
foreach (Sax.OrganisationRole organisationRole in userOrganisation.OrganisationRoles.FindAll(item => item != Sax.OrganisationRole.Member))
data.RoleCodes.Add(OrganisationRoles.GetCodeForType(organisationRole));
data.RoleCount = userOrganisation.OrganisationRoles.Count - 1;
data.StatusCode = organisation.StatusCode;
data.StatusDesc = organisation.StatusDesc;
}
private void PopulateMemberResponseData(MemberData_Full data, SaxUserOrganisation userOrganisation, SaxUserAccount userAccount, SaxOrganisation organisation)
{
PopulateMemberResponseData(data as MemberData_List, userOrganisation, userAccount, organisation);
}
//-------------------------------------------------------------------
// Get Organisation Member List ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_MemberList : AjaxResponse_Data
{
public List<MemberData_List> Members { get; } = new List<MemberData_List>();
public List<CodeDesc> MemberRoles { get; } = new List<CodeDesc>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetMemberList")]
public IActionResult Ajax_GetMemberList(Model_OrganisationMembers model)
{
String fn = GetType().Name + ".Ajax_GetMemberList";
String messageTitle = Localiser["organisation.caption.members"];
AjaxResponse_MemberList responseData = new AjaxResponse_MemberList();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Organisation Id filter is mandatory.
SysId filterOrganisationId = new SysId(StringUtil.ToLong(model.Filter.OrganisationId));
if (!SysId.IsValid(filterOrganisationId))
{
SaLogger.Warn(fn + " Request to retrieve members without valid organisation Id.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
}
// Reference data - organisation roles.
// Exclude 'Member' as this is added automatically when stored.
foreach (TypeCodeDesc organisationRole in OrganisationRoles.FindAll(item => item.Type != (int)Sax.OrganisationRole.Member))
responseData.MemberRoles.Add(organisationRole.Code, Localiser.GetOrganisationRoleCaption((Sax.OrganisationRole)organisationRole.Type));
// Item filter.
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.OrganisationIdsIncluded.Add(filterOrganisationId);
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.NotDeleted;
model.DataItemTotal = (ulong)AccountMgr.GetUserOrganisationCount(prmUserOrganisation);
model.PopulatePagingTo(prmUserOrganisation.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Id);
ColumnDfn colUserAccountId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.UserAccountId);
ColumnDfn colUserEmailAddress = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.User_EmailAddress);
ColumnDfn colUserDisplayName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.User_DisplayName);
ColumnDfn colOrganisationId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Name);
ColumnDfn colRoleCodes = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationRoleCodes);
ColumnDfn colRoleCount = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationRoleCount);
ColumnDfn colStatus = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colUserAccountId,
colUserEmailAddress,
colUserDisplayName,
colOrganisationId,
colOrganisationName,
colRoleCodes,
colRoleCount,
colStatus
};
// Query items.
model.DataItemCount = 0;
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(columns, prmUserOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
model.DataItemCount++;
NamedValue.Map values = resultSet.GetRecordValues();
MemberData_List memberData = new MemberData_List()
{
Id = values.GetString(colId.DataName),
UserAccountId = values.GetString(colUserAccountId.DataName),
EmailAddress = values.GetString(colUserEmailAddress.DataName),
DisplayName = AccountMgr.GetUserOrganisationDataValue(colUserDisplayName, values).ToString(),
OrganisationId = values.GetString(colOrganisationId.DataName),
OrganisationName = values.GetString(colOrganisationName.DataName),
// Role count - 1 to ignore 'Member' role.
RoleCount = (int)values.GetLong(colRoleCount.DataName) - 1,
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Members.Add(memberData);
}
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Organisation Member ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_Member : AjaxResponse_Data
{
public MemberData_Full Member { get; } = new MemberData_Full();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetMember")]
public IActionResult Ajax_GetMember(Model_OrganisationMembers model)
{
String fn = GetType().Name + ".Ajax_GetItem";
String messageTitle = Localiser["organisation.caption.member"];
AjaxResponse_Member responseData = new AjaxResponse_Member();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Identify item.
SysId userOrganisationId = new SysId(StringUtil.ToLong(model.UserOrganisationId));
if (!SysId.IsValid(userOrganisationId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationById(userOrganisationId);
if (userOrganisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Organisation for Id: '" + model.UserOrganisationId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Retrieve User Account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userOrganisation.UserAccountId);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve user account for Id: '" + userOrganisation.UserAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Retrieve Organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(userOrganisation.OrganisationId);
if (organisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve organisation for Id: '" + userOrganisation.OrganisationId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateMemberResponseData(responseData.Member, userOrganisation, userAccount, organisation);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Organisation Member ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/StoreMember")]
public IActionResult Ajax_StoreMember(Model_OrganisationMembers model)
{
String fn = GetType().Name + ".Ajax_StoreMember";
String messageTitle = Localiser["organisation.caption.member"];
AjaxResponse_Member responseData = new AjaxResponse_Member();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Validate item properties.
model.ValidateOrganisationMemberData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// User account.
SysId userAccountId = new SysId(StringUtil.ToLong(model.MemberUserAccountId));
SaxUserAccount userAccount = null;
if (!SysId.IsValid(userAccountId) && !StringUtil.IsEmpty(model.MemberUserEmailAddress))
{
userAccount = AccountMgr.GetUserAccountByEmailAddress(model.MemberUserEmailAddress);
userAccountId = userAccount?.Id ?? new SysId();
}
if (!SysId.IsValid(userAccountId))
{
SaLogger.Warn(fn + " User Account must be identified");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.data.incomplete"]));
}
// Organisation.
SysId organisationId = new SysId(StringUtil.ToLong(model.MemberOrganisationId));
SaxOrganisation organisation = null;
if (!SysId.IsValid(organisationId))
{
SaLogger.Warn(fn + " Organisation Id must be specified");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.data.incomplete"]));
}
// Retrieve existing item.
SysId userOrganisationId = new SysId(StringUtil.ToLong(model.UserOrganisationId));
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationById(userOrganisationId);
if (userOrganisation == null && SysId.IsValid(userOrganisationId))
{
SaLogger.Warn(fn + " Failed to retrieve user organisation for Id: '" + userOrganisationId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
if (userOrganisation != null && (!userOrganisation.UserAccountId.Equals(userAccountId) || !userOrganisation.OrganisationId.Equals(organisationId)))
{
// Either User Account and/or Organisation Ids don't match.
SaLogger.Warn(fn + " Inconsistent user organisation properties. UserAccountId: " + userOrganisation.UserAccountId + " vs " + userAccountId + ". OrganisationId: " + userOrganisation.OrganisationId + " vs " + organisationId + ".");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User Organisation not identified.
if (userOrganisation == null)
{
// See whether user has an 'interest' in the organisation.
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(userAccountId);
prmUserOrganisation.OrganisationIdsIncluded.Add(organisationId);
prmUserOrganisation.OrganisationMembersOnly = false;
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.Any;
userOrganisation = AccountMgr.GetUserOrganisation(prmUserOrganisation);
if (userOrganisation == null)
// Organisation not found for user.
userOrganisation = AccountMgr.CreateUserOrganisation(userAccountId, organisationId);
}
// Retrieve associated items.
if (userAccount == null)
userAccount = AccountMgr.GetUserAccountById(userAccountId);
if (organisation == null)
organisation = AccountMgr.GetOrganisationById(organisationId);
// Populate item properties.
if (model.MemberRoleCodes != null)
userOrganisation.OrganisationRoles = SaxUserOrganisation.GetOrganisationRolesFromCodes(model.GetMemberRoleCodes());
if (!userOrganisation.OrganisationRoles.Contains(Sax.OrganisationRole.Member))
userOrganisation.OrganisationRoles.Add(Sax.OrganisationRole.Member);
userOrganisation.Status = SaStatus.Active;
// Store item.
if (!SysId.IsValid(userOrganisation.Id))
{
if (!AccountMgr.InsertUserOrganisation(userOrganisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user organisation: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
else
{
if (!AccountMgr.UpdateUserOrganisation(userOrganisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user organisation [Id:" + SysId.ToString(userOrganisation.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateMemberResponseData(responseData.Member, userOrganisation, userAccount, organisation);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.Net.Standard.Base.Bus;
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 OrganisationController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public OrganisationController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Organisation model = new Model_Organisation(Localiser);
return View(model);
}
//-------------------------------------------------------------------
/// <summary>
/// Organisation Members action.
/// <para>- POST action to allow Organisation Id to be supplied, eg. from Organsiations page.</para>
/// </summary>
[HttpPost]
public IActionResult Members(Model_OrganisationMembers model)
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Retrieve organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(new SysId(StringUtil.ToLong(model.Filter.OrganisationId)));
if (organisation == null)
RedirectToAction("", "Organisation");
model.MemberOrganisationName = organisation?.Name;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
public class PoliciesController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public PoliciesController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
Model_Policies model = new Model_Policies(Localiser);
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Privacy()
{
Model_Policies model = new Model_Policies(Localiser);
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,241 @@
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 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 RegionController
{
//-------------------------------------------------------------------
// Region data.
public class RegionData_List
{
public String Id { get; set; }
public String RegionTypeCode { get; set; }
public String RegionTypeDesc { get; set; }
public String CountryIso2Code { get; set; }
public String Name { get; set; }
public String ParentName { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public class RegionData_Full : RegionData_List
{
}
private void PopulateRegionResponseData(SaxRegion region, RegionData_List data)
{
data.Id = SysId.ToString(region.Id);
data.RegionTypeCode = RegionTypes.GetCodeForType(region.RegionType);
data.RegionTypeDesc = RegionTypes.GetDescForType(region.RegionType);
data.CountryIso2Code = region.CountryIso2Code;
data.Name = region.Name;
data.StatusCode = region.StatusCode;
data.StatusDesc = region.StatusDesc;
}
private void PopulateRegionResponseData(SaxRegion region, RegionData_Full data)
{
PopulateRegionResponseData(region, data as RegionData_List);
}
//-------------------------------------------------------------------
// Get Region List ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_RegionList : AjaxResponse_Data
{
public List<RegionData_List> Regions { get; } = new List<RegionData_List>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Region/Ajax/GetList")]
public IActionResult Ajax_GetList(Model_Region model)
{
String fn = GetType().Name + ".Ajax_GetList";
String messageTitle = Localiser["region.caption.regions"];
AjaxResponse_RegionList responseData = new AjaxResponse_RegionList();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Item filter.
SaxRegion.Params prmRegion = new SaxRegion.Params();
prmRegion.RegionFilter = model.Filter.Region;
if (model.Filter.RegionTypeCode != null)
prmRegion.RegionType = model.FilterRegionType;
prmRegion.StatusCat = SaxRegion.StatusCat.NotDeleted;
model.DataItemTotal = (ulong)ReferenceMgr.GetRegionCount(prmRegion);
model.PopulatePagingTo(prmRegion.ResultsPaging);
// Item columns.
ColumnDfn colId = ReferenceMgr.GetRegionColumnDfn(SaxRegion.Column.Id);
ColumnDfn colType = ReferenceMgr.GetRegionColumnDfn(SaxRegion.Column.RegionType);
ColumnDfn colIso2Code = ReferenceMgr.GetRegionColumnDfn(SaxRegion.Column.CountryIso2Code);
ColumnDfn colName = ReferenceMgr.GetRegionColumnDfn(SaxRegion.Column.Name);
ColumnDfn colParentName = ReferenceMgr.GetRegionColumnDfn(SaxRegion.Column.Parent_Name);
ColumnDfn colStatus = ReferenceMgr.GetRegionColumnDfn(SaxRegion.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colType,
colIso2Code,
colName,
colParentName,
colStatus
};
// Sort order.
prmRegion.SortColumns.Add(colName);
// Query items.
model.DataItemCount = 0;
using (DbResultSet resultSet = ReferenceMgr.ExecuteRegionQuery(columns, prmRegion))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
model.DataItemCount++;
NamedValue.Map values = resultSet.GetRecordValues();
Sax.RegionType regionType = (Sax.RegionType)values.GetLong(colType.DataName);
RegionData_List regionData = new RegionData_List()
{
Id = values.GetString(colId.DataName),
RegionTypeCode = RegionTypes.GetCodeForType(regionType),
RegionTypeDesc = RegionTypes.GetDescForType(regionType),
CountryIso2Code = values.GetString(colIso2Code.DataName),
Name = values.GetString(colName.DataName),
ParentName = values.GetString(colParentName.DataName),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Regions.Add(regionData);
}
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Region ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_Region : AjaxResponse_Data
{
public RegionData_Full region { get; } = new RegionData_Full();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Region/Ajax/GetItem")]
public IActionResult Ajax_GetItem(Model_Region model)
{
String fn = GetType().Name + ".Ajax_GetItem";
String messageTitle = Localiser["region.caption.region"];
AjaxResponse_Region responseData = new AjaxResponse_Region();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Identify item.
if (!SysId.IsValid(model.RegionId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxRegion region = ReferenceMgr.GetRegionById(model.RegionId);
if (region == null)
{
SaLogger.Warn(fn + " Failed to retrieve region for Id: '" + model.RegionIdValue + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateRegionResponseData(region, responseData.region);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Region ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Region/Ajax/StoreItem")]
public IActionResult Ajax_StoreItem(Model_Region model)
{
String fn = GetType().Name + ".Ajax_StoreItem";
String messageTitle = Localiser["region.caption.region"];
AjaxResponse_Region responseData = new AjaxResponse_Region();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Validate item properties.
model.ValidateRegionData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve existing item.
SaxRegion region = ReferenceMgr.GetRegionById(model.RegionId);
if (region == null && SysId.IsValid(model.RegionId))
{
SaLogger.Warn(fn + " Failed to retrieve region for Id: '" + model.RegionIdValue + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Create new item if required.
if (region == null)
region = ReferenceMgr.CreateRegion();
// Populate item properties.
if (model.RegionCountryIso2Code != null)
region.CountryIso2Code = model.RegionCountryIso2Code;
if (model.RegionName != null)
region.Name = model.RegionName;
if (model.RegionStatusCode != null)
region.Status = model.RegionStatus;
// Store item.
if (!SysId.IsValid(region.Id))
{
if (!ReferenceMgr.InsertRegion(region, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert region: " + ReferenceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ReferenceMgr.ErrorStateMessage));
}
}
else
{
if (!ReferenceMgr.UpdateRegion(region, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update region [Id:" + SysId.ToString(region.Id) + "]: " + ReferenceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ReferenceMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateRegionResponseData(region, responseData.region);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
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 RegionController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public RegionController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Region model = new Model_Region(Localiser);
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,186 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
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.Net.Core.Web.Admin.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;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
public class SaxController_Admin : SaxController_Base
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Protected construction.
protected SaxController_Admin(SaxLocaliser localiser, ILogger<HomeController> logger) : base(localiser)
{
}
//-------------------------------------------------------------------
// Session data.
//-------------------------------------------------------------------
/// <summary>Admin session data</summary>
protected SaxSessionData_Admin SessionData_Admin { get { return SessionData as SaxSessionData_Admin; } }
protected override SaxSessionData DeserializeSessionData(String sessionDataJson)
{
return JsonConvert.DeserializeObject<SaxSessionData_Admin>(sessionDataJson, SaxSessionDataJsonSerializerSettings);
}
//-------------------------------------------------------------------
// User Session.
//-------------------------------------------------------------------
/// <summary>
/// Sign the account in.
/// <para>- Record login time and clear invalid login data.</para>
/// <para>- Open user session - store details against Sesion Data.</para>
/// <para>- Set 'Keep logged in' cookie if required.</para>
/// </summary>
protected bool SignUserIn(SaxUserAccount userAccount, List<SaxUserRole> userRoles, bool keepLoggedIn, ref String errorMessage)
{
String fn = GetType().Name + ".SignUserIn";
// Validate User Account, just in case - should have been done before arriving here!
if (userAccount == null)
{
SaLogger.Error(fn + " Unable to sign account in - null user account.");
errorMessage = Localiser["security.msg.signin.failed"];
return false;
}
if (!userAccount.IsStatusActive)
{
SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.failed"];
return false;
}
// 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.SessionConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
// Log user in.
userAccount.KeepLoggedIn_Admin = keepLoggedIn;
userAccount.LastLoginAt = DateTime.Now;
userAccount.InvalidLoginCount = 0;
userAccount.LastInvalidLoginAt = DateTime.MinValue;
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update account for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
errorMessage = Localiser["security.msg.signin.failed"];
return false;
}
SaLogger.Info(fn + " User session opened for " + userAccount.EmailAddress, true);
// Create session.
OpenUserSession(SessionContext, userAccount, userRoles);
// Send the 'Keep Logged In' cookie if requested.
if (userAccount.KeepLoggedIn_Admin)
SetKeepLoggedInCookie();
return true;
}
//-------------------------------------------------------------------
/// <summary>Start a new user session on sign-in.</summary>
protected bool OpenUserSession(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles)
{
// Validate user properties.
if (!SysId.IsValid(userAccount.Id)) return false;
if (StringUtil.IsEmpty(userAccount.EmailAddress)) return false;
// Close previous session.
CloseUserSession();
// Open session.
return SetSessionData(new SaxSessionData_Admin(sessionContext, userAccount, userRoles));
}
protected void CloseUserSession()
{
ClearSessionData();
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
/// <summary>Keep-Logged-In cookie.</summary>
protected void SetKeepLoggedInCookie()
{
String keepLoggedInCookieValue = GenerateKeepLoggedInCookieValue();
int cookieExpiresDays = Math.Max(Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Min, Math.Min(Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Max, SessionConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays));
SetResponseCookie(CookieName_KeepLoggedIn, keepLoggedInCookieValue, DateTime.Now.AddDays(cookieExpiresDays), true);
SaLogger.Info(this.GetType().Name + ".SetKeepLoggedInCookie 'Keep Logged In' cookie set. Value: " + keepLoggedInCookieValue);
}
/// <summary>Set the 'Keep Logged In' response cookie to expired with no data.</summary>
public void ClearKeepLoggedInCookie()
{
if (StringUtil.IsEmpty(GetRequestCookie(CookieName_KeepLoggedIn))) return;
// Clear cookie by setting expiry date in the past.
SetResponseCookie(CookieName_KeepLoggedIn, "", DateTime.Today.AddDays(-7), true);
SaLogger.Info(GetType().Name + ".ClearKeepLoggedInCookie 'Keep Logged In' cookie cleared.");
}
//-------------------------------------------------------------------
// 'Keep Logged In' cookie and encryption config.
// NB - IV value is generated from the key and fixed so that it doesn't change when the application restarts
// to ensure that cookie value can be decrypted even if application restarts in the meantime.
// - Generating IV from same key results in different value each time.
// - IV Must be regenerated if key is changed: uncomment 'Generated' variable and copy resulting value to IV variable.
// NB
public static String CookieName_KeepLoggedIn = "SXKLI";
public static String Base64Key_KeepLoggedIn = SacsEncryptionHelper.EncodePlainKey("<=Saxonite^{kEEp+1Ogg3d-IN}~32*>", SacsEncryptionHelper.KeyLength);
public static String Base64IV_KeepLoggedIn_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(Base64Key_KeepLoggedIn);
public static String Base64IV_KeepLoggedIn = "YOX10FIPp1zM1pg5U6fW7A==";
//-------------------------------------------------------------------
// Keep-logged-in cookie value.
private static readonly String ParamName_KeepLoggedIn_UserAccountId = "uai";
private String GenerateKeepLoggedInCookieValue()
{
NameValueCollection keepLoggedInParams = new NameValueCollection();
keepLoggedInParams.Add(ParamName_KeepLoggedIn_UserAccountId, SysId.ToString(SessionData_Admin.User.UserAccountId));
return EncryptParameters(keepLoggedInParams, Base64IV_KeepLoggedIn, Base64Key_KeepLoggedIn);
}
//-------------------------------------------------------------------
public class KeepLoggedInParams
{
public SysId UserAccountId { get; set; }
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
public List<String> GetValidParams()
{
List<String> values = new List<String>();
if (SysId.IsValid(UserAccountId))
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
return values;
}
}
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
{
KeepLoggedInParams keepLoggedInParams = new KeepLoggedInParams();
if (!StringUtil.IsEmpty(encryptedParams))
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_KeepLoggedIn, Base64Key_KeepLoggedIn);
keepLoggedInParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId)));
}
if (DebugMode) SaLogger.Info(GetType().Name + ".ExtractKeepLoggedInParams UserAccountId: " + SysId.ToString(keepLoggedInParams.UserAccountId));
return keepLoggedInParams;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,448 @@
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.Email;
using Sarand.Net.Standard.Base.Helpers;
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 Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
partial class SecurityController
{
//-------------------------------------------------------------------
// Sign-in request.
//-------------------------------------------------------------------
public class AjaxResponse_Signin : AjaxResponse_Data
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/Signin")]
public IActionResult Ajax_Signin(Model_Security model)
{
String fn = GetType().Name + ".Ajax_Signin";
AjaxResponse_Signin responseData = new AjaxResponse_Signin();
String messageTitle = Localiser["signin.caption"];
// Validate Sign-in data.
model.ValidateSignInData(responseData);
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))
{
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
// Construct response.
responseData.Success = SessionData_Admin?.ValidUserSession ?? false;
return Json(responseData);
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
public class AjaxResponse_KeepLoggedIn : AjaxResponse_Base
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/KeepLoggedIn")]
public ActionResult Ajax_KeepLoggedIn(Model_Security model)
{
String fn = GetType().Name + ".Ajax_KeepLoggedIn";
AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn();
if (!ValidUserSession)
return Json(responseData.SetGoHome());
if (!SessionConfig.Security.SignIn.KeepLoggedIn.Allowed)
return Json(responseData);
// Resend the cookie to extend the expiry date if it is currently populated.
if (!StringUtil.IsEmpty(GetRequestCookie(CookieName_KeepLoggedIn)))
SetKeepLoggedInCookie();
// Construct response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
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))
{
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"]);
}
return userAccount;
}
private void recordInvalidLoginAttempt (Model_Security model, SaxUserAccount userAccount)
{
String fn = GetType().Name + ".recordInvalidLoginAttempt";
// Record invalid login attempt.
userAccount.InvalidLoginCount++;
userAccount.LastInvalidLoginAt = DateTime.Now;
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User Account for " + model.EmailAddress + ": " + AccountMgr.ErrorStateMessage, true);
}
//-------------------------------------------------------------------
// Sign-out request.
//-------------------------------------------------------------------
public class AjaxResponse_SignOut : AjaxResponse_Data
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/SignOut")]
public ActionResult Ajax_SignOut(Model_Security model)
{
String fn = GetType().Name + ".Ajax_SignOut";
AjaxResponse_SignOut responseData = new AjaxResponse_SignOut();
String messageTitle = Localiser["signin.caption.signout"];
// User must have a valid session.
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)
{
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();
// Force return to home page.
responseData.SetGoHome(); // (messageTitle, Localiser["security.msg.signout"]);
return Json(responseData);
}
//-------------------------------------------------------------------
// Password complexity request.
//-------------------------------------------------------------------
public class AjaxResponse_PasswordScore : AjaxResponse_Data
{
public int Score { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/ScorePassword")]
public ActionResult Ajax_ScorePassword(Model_Security model)
{
// No need to store the session data for this request.
StoreSessionData = false;
// Score pasword.
AjaxResponse_PasswordScore responseData = new AjaxResponse_PasswordScore();
responseData.Score = SaxPasswordHelper.GetPasswordStrength(model.Password);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Change Password request.
//-------------------------------------------------------------------
public class AjaxResponse_ChangePassword : AjaxResponse_Data
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/StorePassword")]
public ActionResult Ajax_StorePassword(Model_Security model)
{
String fn = GetType().Name + ".Ajax_StorePassword";
AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword();
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())
{
// Retrieve user account details and validate email address.
userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId);
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;
}
}
// 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)
{
userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId);
}
if (userAccount == null)
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details."));
// 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]))
{
// 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);
// Construct response.
responseData.Success = true;
if (validRequestPasswordParams)
responseData.SetMessage(messageTitle, Localiser["security.msg.request.password.saved"]);
return Json(responseData);
}
//-------------------------------------------------------------------
// Request Password request.
//-------------------------------------------------------------------
public class AjaxResponse_RequestPassword : AjaxResponse_Data
{
/// <summary>Message to be displayed for the user to 'OK' before returning to the Sign-in page.</summary>
public String SuccessMsg { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/RequestNewPassword")]
public ActionResult Ajax_RequestNewPassword(Model_Security model)
{
String fn = GetType().Name + ".Ajax_RequestNewPassword";
AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword();
String messageTitle = Localiser["security.caption.request.password"];
// Validate data.
model.ValidateRequestPasswordData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve user account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountByEmailAddress(model.EmailAddress);
if (userAccount != null && (userAccount.IsStatusActive || DebugMode))
{
// Send password request email.
SaEmailData emailData = new SaEmailData();
emailData.Config = EmailConfig;
emailData.AddressesTo.Add(model.EmailAddress);
emailData.Subject = Localiser["security.caption.request.password"];
PopulateRequestPasswordEmailBody(emailData, userAccount);
if (EmailConsole.SendEmail(emailData))
SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true);
else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress);
}
else
{
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");
}
// Construct response.
responseData.Success = true;
responseData.SuccessMsg = Localiser["security.msg.request.password.email.sent"];
return Json(responseData);
}
private void PopulateRequestPasswordEmailBody(SaEmailData emailData, SaxUserAccount userAccount)
{
// Will need to handle culture (language/country) as and when introduced.
emailData.MessageBody = SaxResourceHelper.GetTextResource(SaxResourceHelper.TextResource.Email_RequestPassword_Html);
emailData.HtmlBody = true;
// Replace dynamic tags.
EmailConsole.ReplaceDynamicEmailBodyTags(emailData, null, userAccount);
// Replace dynamic tag for link back to change password page.
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.RequestPassword]", GenerateRequestPasswordLink(userAccount.Id, userAccount.EmailAddress, ""));
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.ExpiresMinutes]", GetRequestPasswordEmailLinkExpiresMinutes().ToString());
}
//-------------------------------------------------------------------
// Password request link.
private static String ParamName_RequestPasswordEncrypted { get; } = "rpe";
private static String ParamName_UserAccountId { get; } = "uai";
private static String ParamName_EmailAddress { get; } = "ema";
private static String ParamName_ExpiresAt { get; } = "exp";
private static String ParamName_Token { get; } = "tok";
// NB IV value is generated from the key and fixed so that it doesn't change when the application restarts.
// IV Must be regenerated if key is changed.
private static String Base64Key_RequestPassword { get; } = SacsEncryptionHelper.EncodePlainKey("{~Saxonite@@Request^Password=}", SacsEncryptionHelper.KeyLength);
private static String Base64IV_RequestPassword_Generated { get; } = SacsEncryptionHelper.InitSymmetricEncryptionIV(Base64Key_RequestPassword);
private static String Base64IV_RequestPassword { get; } = "1VAWYTW6d72n/6HDNhbqow==";
private String GenerateRequestPasswordLink(SysId userAccountId, String emailAddress, String requestPasswordToken)
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ParamName_UserAccountId, SysId.ToString(userAccountId));
parameters.Add(ParamName_EmailAddress, emailAddress);
parameters.Add(ParamName_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes()), DateUtil.Format_XmlDateTime));
parameters.Add(ParamName_Token, requestPasswordToken);
String link =
GetRedirectLocation(RedirectLocation.WebAppBase) + "Security/ChangePassword?" +
ParamName_RequestPasswordEncrypted + "=" + HttpUtility.UrlEncode(EncryptParameters(parameters, Base64IV_RequestPassword, Base64Key_RequestPassword));
return link;
}
private int GetRequestPasswordEmailLinkExpiresMinutes()
{
int expiresMinutes = SessionConfig.Security.SignIn.PasswordRequest.EmailLinkExpiresMins;
int expiresMinutes_Min = Sax.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Min;
int expiresMinutes_Max = Sax.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Max;
return Math.Max(expiresMinutes_Min, Math.Min(expiresMinutes_Max, expiresMinutes));
}
public class RequestPasswordParams
{
public SysId UserAccountId { get; set; }
public String EmailAddress { get; set; }
public DateTime ExpiresAt { get; set; }
public String Token { get; set; }
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
public List<String> GetValidParams()
{
List<String> values = new List<String>();
if (SysId.IsValid(UserAccountId))
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
if (!StringUtil.IsEmpty(EmailAddress))
values.Add("Email: " + EmailAddress);
if (DateUtil.IsValid(ExpiresAt))
values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt));
if (!StringUtil.IsEmpty(Token))
values.Add("Token: " + Token);
return values;
}
/// <summary>Verifiy that the account Id and email are populated and the data hasn't expired.</summary>
public bool IsValid()
{
return
SysId.IsValid(UserAccountId) &&
!StringUtil.IsEmpty(EmailAddress) &&
DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now;
}
/// <summary>Verify that the data is valid and the account details match.</summary>
public bool IsValid(SaxUserAccount userAccount)
{
return
IsValid() &&
userAccount != null &&
userAccount.Id.Equals(UserAccountId) &&
userAccount.EmailAddress.EqualsCI(EmailAddress);
}
}
private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams)
{
RequestPasswordParams requestPasswordParams = new RequestPasswordParams();
if (!StringUtil.IsEmpty(encryptedParams))
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_RequestPassword, Base64Key_RequestPassword);
requestPasswordParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_UserAccountId)));
requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress);
requestPasswordParams.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt));
requestPasswordParams.Token = linkParams.Get(ParamName_Token);
}
return requestPasswordParams;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,121 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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.Net.Core.Web.Admin.App;
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.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
public partial class SecurityController : SaxController_Admin
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SecurityController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
return RedirectToAction("Signin", "Security");
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Signin()
{
// Redirect home if signed-in.
if (ValidUserSession)
return RedirectToAction("", "");
Model_Security model = new Model_Security(Localiser);
return View(model);
}
//-------------------------------------------------------------------
// Security - Change password
/// <summary>
/// Security - Change Password action.
/// <para>- GET from request password email link.</para>
/// <para>- POST with user signed-in. Browser cache prohibited.</para>
/// </summary>
[HttpGet, HttpPost]
public IActionResult ChangePassword(Model_Security model)
{
String fn = GetType().Name + ".ChangePassword";
bool validSessionRequired = true;
if (Request.Method.EqualsCI(HttpMethods.Get))
{
// Check for encrypted parameters from 'Request Password' email link.
// NB Only allowed as a 'GET' request.
String encryptedParams = Request.Query[ParamName_RequestPasswordEncrypted];
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams);
if (requestPasswordParams.IsValid())
{
String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", ".");
using (SaxUserAccount userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId))
{
if (requestPasswordParams.IsValid(userAccount))
{
model.rpe = encryptedParams;
validSessionRequired = false;
}
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. " + logSuffix);
}
}
}
// Must have a valid session to change password unless linked from Request Password above.
if (validSessionRequired && !ValidUserSession)
return RedirectToAction("", "");
// Configure page.
return View(model);
}
//-------------------------------------------------------------------
// Security - Request Password
//-------------------------------------------------------------------
/// <summary>
/// Sign-in Request Password action.
/// <para>- User not signed-in.</para>
/// <para>- GET only with browser cache allowed.</para>
/// </summary>
[HttpGet]
public IActionResult RequestPassword()
{
Model_Security model = new Model_Security(Localiser);
// Configure page.
model.ProhibitCache = false;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,219 @@
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 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 SportController
{
//-------------------------------------------------------------------
// Sport data.
public class SportData_List
{
public String Id { get; set; }
public String Name { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public class SportData_Full : SportData_List
{
}
private void PopulateSportResponseData(SaxSport sport, SportData_List data)
{
data.Id = SysId.ToString(sport.Id);
data.Name = sport.Name;
data.StatusCode = sport.StatusCode;
data.StatusDesc = sport.StatusDesc;
}
private void PopulateSportResponseData(SaxSport sport, SportData_Full data)
{
PopulateSportResponseData(sport, data as SportData_List);
}
//-------------------------------------------------------------------
// Get Sport List ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_SportList : AjaxResponse_Data
{
public List<SportData_List> Sports { get; } = new List<SportData_List>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Sport/Ajax/GetList")]
public IActionResult Ajax_GetList(Model_Sport model)
{
String fn = GetType().Name + ".Ajax_GetList";
String messageTitle = Localiser["sport.caption.sports"];
AjaxResponse_SportList responseData = new AjaxResponse_SportList();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Item filter.
SaxSport.Params prmSport = new SaxSport.Params();
prmSport.SportFilter = model.Filter.Sport;
prmSport.StatusCat = SaxSport.StatusCat.NotDeleted;
model.DataItemTotal = (ulong)ReferenceMgr.GetSportCount(prmSport);
model.PopulatePagingTo(prmSport.ResultsPaging);
// Item columns.
ColumnDfn colId = ReferenceMgr.GetSportColumnDfn(SaxSport.Column.Id);
ColumnDfn colName = ReferenceMgr.GetSportColumnDfn(SaxSport.Column.Name);
ColumnDfn colStatus = ReferenceMgr.GetSportColumnDfn(SaxSport.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colName,
colStatus
};
// Sort order.
prmSport.SortColumns.Add(colName);
// Query items.
model.DataItemCount = 0;
using (DbResultSet resultSet = ReferenceMgr.ExecuteSportQuery(columns, prmSport))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
model.DataItemCount++;
NamedValue.Map values = resultSet.GetRecordValues();
SportData_List sportData = new SportData_List()
{
Id = values.GetString(colId.DataName),
Name = values.GetString(colName.DataName),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Sports.Add(sportData);
}
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Sport ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_Sport : AjaxResponse_Data
{
public SportData_Full sport { get; } = new SportData_Full();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Sport/Ajax/GetItem")]
public IActionResult Ajax_GetItem(Model_Sport model)
{
String fn = GetType().Name + ".Ajax_GetItem";
String messageTitle = Localiser["sport.caption.sport"];
AjaxResponse_Sport responseData = new AjaxResponse_Sport();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Identify item.
if (!SysId.IsValid(model.SportId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxSport sport = ReferenceMgr.GetSportById(model.SportId);
if (sport == null)
{
SaLogger.Warn(fn + " Failed to retrieve sport for Id: '" + model.SportIdValue + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateSportResponseData(sport, responseData.sport);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Sport ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Sport/Ajax/StoreItem")]
public IActionResult Ajax_StoreItem(Model_Sport model)
{
String fn = GetType().Name + ".Ajax_StoreItem";
String messageTitle = Localiser["sport.caption.sport"];
AjaxResponse_Sport responseData = new AjaxResponse_Sport();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Validate item properties.
model.ValidateSportData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve existing item.
SaxSport sport = ReferenceMgr.GetSportById(model.SportId);
if (sport == null && SysId.IsValid(model.SportId))
{
SaLogger.Warn(fn + " Failed to retrieve sport for Id: '" + model.SportIdValue + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Create new item if required.
if (sport == null)
sport = ReferenceMgr.CreateSport();
// Populate item properties.
if (model.SportName != null)
sport.Name = model.SportName;
if (model.SportStatusCode != null)
sport.Status = model.SportStatus;
// Store item.
if (!SysId.IsValid(sport.Id))
{
if (!ReferenceMgr.InsertSport(sport, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert sport: " + ReferenceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ReferenceMgr.ErrorStateMessage));
}
}
else
{
if (!ReferenceMgr.UpdateSport(sport, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update sport [Id:" + SysId.ToString(sport.Id) + "]: " + ReferenceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ReferenceMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateSportResponseData(sport, responseData.sport);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
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 SportController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public SportController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Sport model = new Model_Sport(Localiser);
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,318 @@
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 UserController
{
//-------------------------------------------------------------------
// User data.
public class UserData_List
{
public String Id { get; set; }
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 int UserRoleCount { get; set; }
public bool SystemAdmin { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public class UserData_Full : UserData_List
{
public List<String> userRoleCodes { get; } = new List<string>();
}
private void PopulateUserResponseData(UserData_List data, SaxUserAccount user, List<SaxUserRole> userRoles)
{
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.UserRoleCount = userRoles?.GetUserRolesByStatus(SaStatus.Active).Count ?? 0;
data.StatusCode = user.StatusCode;
data.StatusDesc = user.StatusDesc;
}
private void PopulateUserResponseData(UserData_Full data, SaxUserAccount user, List<SaxUserRole> userRoles)
{
PopulateUserResponseData(data as UserData_List, user, userRoles);
if (userRoles != null)
{
foreach (SaxUserRole userRole in userRoles.GetUserRolesByStatus(SaStatus.Active))
data.userRoleCodes.Add(UserRoles.GetCodeForType(userRole.UserRole));
}
}
//-------------------------------------------------------------------
// Get User List ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_UserList : AjaxResponse_Data
{
public List<UserData_List> Users { get; } = new List<UserData_List>();
public List<CodeDesc> UserRoles { get; } = new List<CodeDesc>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("User/Ajax/GetList")]
public IActionResult Ajax_GetList(Model_User model)
{
String fn = GetType().Name + ".Ajax_GetList";
String messageTitle = Localiser["user.caption.users"];
AjaxResponse_UserList responseData = new AjaxResponse_UserList();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Reference data - user roles.
foreach (TypeCodeDesc userRole in UserRoles)
responseData.UserRoles.Add(userRole.Code, Localiser.GetUserRoleCaption((Sax.UserRole)userRole.Type));
// Item filter.
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
prmUserAccount.UserAccountFilter = model.Filter.User;
prmUserAccount.StatusCat = SaxUserAccount.StatusCat.NotDeleted;
model.DataItemTotal = (ulong)AccountMgr.GetUserAccountCount(prmUserAccount);
model.PopulatePagingTo(prmUserAccount.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Id);
ColumnDfn colEmail = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.EmailAddress);
ColumnDfn colTitle = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Title);
ColumnDfn colFirstName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.FirstName);
ColumnDfn colLastName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastName);
ColumnDfn colUserRoleCount = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.UserRoleCount);
ColumnDfn colSystemAdminUser = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.SystemAdminUser);
ColumnDfn colStatus = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colEmail,
colTitle,
colFirstName,
colLastName,
colUserRoleCount,
colSystemAdminUser,
colStatus
};
// Query items.
model.DataItemCount = 0;
using (DbResultSet resultSet = AccountMgr.ExecuteUserAccountQuery(columns, prmUserAccount))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
model.DataItemCount++;
NamedValue.Map values = resultSet.GetRecordValues();
UserData_List userData = new UserData_List()
{
Id = values.GetString(colId.DataName),
EmailAddress = values.GetString(colEmail.DataName),
Title = values.GetString(colTitle.DataName),
FirstName = values.GetString(colFirstName.DataName),
LastName = values.GetString(colLastName.DataName),
UserRoleCount = StringUtil.ToInt(values.GetString(colUserRoleCount.DataName)),
SystemAdmin = StringUtil.ToBool(values.GetString(colSystemAdminUser.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
userData.DisplayName = SaxUserAccount.GetDisplayName(userData.Title, userData.FirstName, userData.LastName);
responseData.Users.Add(userData);
}
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User ajax request.
//-------------------------------------------------------------------
public class AjaxResponse_User : AjaxResponse_Data
{
public UserData_Full user { get; } = new UserData_Full();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("User/Ajax/GetItem")]
public IActionResult Ajax_GetItem(Model_User model)
{
String fn = GetType().Name + ".Ajax_GetItem";
String messageTitle = Localiser["user.caption.user"];
AjaxResponse_User responseData = new AjaxResponse_User();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Identify item.
if (!SysId.IsValid(model.UserAccountId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserAccount user = AccountMgr.GetUserAccountById(model.UserAccountId);
if (user == null)
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountIdValue + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User's roles.
// Construct response.
PopulateUserResponseData(responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active));
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
private List<SaxUserRole> GetUserRoles(SysId userAccountId, SaxUserRole.StatusCat statusCat = SaxUserRole.StatusCat.NotDeleted)
{
if (!SysId.IsValid(userAccountId)) return new List<SaxUserRole>();
SaxUserRole.Params prmUserRole = new SaxUserRole.Params();
prmUserRole.UserAccountIdsIncluded.Add(userAccountId);
prmUserRole.StatusCat = statusCat;
return AccountMgr.GetUserRoleList(prmUserRole);
}
//-------------------------------------------------------------------
// Store User ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("User/Ajax/StoreItem")]
public IActionResult Ajax_StoreItem(Model_User model)
{
String fn = GetType().Name + ".Ajax_StoreItem";
String messageTitle = Localiser["user.caption.user"];
AjaxResponse_User responseData = new AjaxResponse_User();
// Validate session.
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
// Validate item properties.
model.ValidateUserData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve existing item.
SaxUserAccount user = AccountMgr.GetUserAccountById(model.UserAccountId);
if (user == null && SysId.IsValid(model.UserAccountId))
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountIdValue + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User roles, including deleted.
List<String> userRoleCodes = model.GetUserRoleCodes();
List<SaxUserRole> userRoles = new List<SaxUserRole>();
if (user != null) userRoles = GetUserRoles(user.Id, SaxUserRole.StatusCat.Any);
foreach (String userRoleCode in userRoleCodes)
{
// Role to activate.
Sax.UserRole role = (Sax.UserRole)UserRoles.GetTypeForCode(userRoleCode);
SaxUserRole userRole = userRoles.GetUserRoleByRole(role);
if (userRole == null)
{
userRole = AccountMgr.CreateUserRole(user?.Id, role);
userRoles.Add(userRole);
}
userRole.Status = SaStatus.Active;
}
// Roles to deactivate.
foreach (SaxUserRole userRole in userRoles)
{
if (!userRoleCodes.Exists(item => item.EqualsCI(UserRoles.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));
}
// Apply User Account Id to dependent items.
foreach (SaxUserRole userRole in (userRoles))
userRole.UserAccountId = user.Id;
}
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));
}
}
// Associated data - User Roles.
foreach (SaxUserRole userRole in (userRoles))
{
// Store User Role.
if (!SysId.IsValid(userRole.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(responseData.user, user, userRoles.GetUserRolesByStatus(SaStatus.Active));
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
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 UserController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public UserController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_User model = new Model_User(Localiser);
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,16 @@
using System;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class ErrorViewModel
{
//-------------------------------------------------------------------
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,23 @@
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class Model_Home : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_Home(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,103 @@
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.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.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class Model_Organisation : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_Organisation(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_JS.Add("js/sax-organisation.js");
}
//-------------------------------------------------------------------
// List configuration.
public class FilterData
{
public String Organisation { get; set; }
public String OrganisationTypeCode { get; set; }
public String OrganisationCategoryCode { get; set; }
}
public FilterData Filter { get; } = new FilterData();
public Sax.OrganisationType FilterOrganisationType
{
get { return (Sax.OrganisationType)OrganisationTypes.GetTypeForCode(Filter.OrganisationTypeCode); }
}
public Sax.OrganisationCategory FilterOrganisationCategory
{
get { return (Sax.OrganisationCategory)OrganisationCategories.GetTypeForCode(Filter.OrganisationCategoryCode); }
}
//-------------------------------------------------------------------
// Organisation properties.
public String OrganisationId { get; set; }
public String OrganisationName { get; set; }
public String OrganisationTypeValue { get; set; }
public Sax.OrganisationType OrganisationType { get { return (Sax.OrganisationType)OrganisationTypes.GetTypeForCode(OrganisationTypeValue); } }
public String OrganisationCategoryValue { get; set; }
public Sax.OrganisationCategory OrganisationCategory { get { return (Sax.OrganisationCategory)OrganisationCategories.GetTypeForCode(OrganisationCategoryValue); } }
public String OrganisationStatusCode { get; set; }
public SaStatus OrganisationStatus { get { return SacsSystem.GetStatusForCode(OrganisationStatusCode); } }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Organisation validation.
public bool ValidateOrganisationData(SaxController_Base.AjaxResponse_Data responseData)
{
// Organisation name.
if (OrganisationName != null)
{
responseData.SetControlValue(nameof(OrganisationName), OrganisationName);
if (StringUtil.IsEmpty(OrganisationName))
responseData.SetControlError(nameof(OrganisationName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(OrganisationName) > GetControlMaxLength(SaxDbDfn.ColumnIdx.Organisation_Name))
responseData.SetControlError(nameof(OrganisationName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn.ColumnIdx.Organisation_Name)]);
}
// Organisation type.
if (OrganisationTypeValue != null)
{
if (StringUtil.IsEmpty(OrganisationTypeValue))
responseData.SetControlError(nameof(OrganisationTypeValue), Localiser["msg.validation.mandatory.value"]);
}
// Organisation category.
if (OrganisationCategoryValue != null)
{
if (StringUtil.IsEmpty(OrganisationCategoryValue))
responseData.SetControlError(nameof(OrganisationCategoryValue), Localiser["msg.validation.mandatory.value"]);
}
// Organisation status.
if (OrganisationStatusCode != null)
{
if (StringUtil.IsEmpty(OrganisationStatusCode))
responseData.SetControlError(nameof(OrganisationStatusCode), Localiser["msg.validation.mandatory.value"]);
else if (!SaxOrganisation.ValidStatuses.Contains(OrganisationStatus))
responseData.SetControlError(nameof(OrganisationStatusCode), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,100 @@
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.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_OrganisationMembers : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_OrganisationMembers(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_JS.Add("js/sax-organisationMembers.js");
}
//-------------------------------------------------------------------
// List configuration.
public class FilterData
{
public String OrganisationId { get; set; }
}
public FilterData Filter { get; } = new FilterData();
public String OrganisationId { get { return Filter.OrganisationId; } set { Filter.OrganisationId = value; } }
//-------------------------------------------------------------------
// User Organisation (member) properties.
public String UserOrganisationId { get; set; }
[EmailAddress]
public String MemberUserEmailAddress{ get; set; }
public String MemberUserAccountId { get; set; }
public String MemberOrganisationId { get; set; }
public String MemberOrganisationName { get; set; }
public String MemberRoleCodeDelim = ",";
public List<String> MemberRoleCodes { get; set; }
public List<String> GetMemberRoleCodes()
{
List<String> codes = new List<string>();
foreach (String code in MemberRoleCodes)
{
if (StringUtil.IsEmpty(code)) continue;
codes.AddRange(code.Split(MemberRoleCodeDelim));
}
return codes;
}
public String MemberStatusCode { get; set; }
public SaStatus MemberStatus { get { return SacsSystem.GetStatusForCode(MemberStatusCode); } }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Organisation member validation.
public bool ValidateOrganisationMemberData(SaxController_Base.AjaxResponse_Data responseData)
{
// User email address.
if (MemberUserEmailAddress != null)
{
responseData.SetControlValue(nameof(MemberUserEmailAddress), MemberUserEmailAddress);
if (StringUtil.IsEmpty(MemberUserEmailAddress))
responseData.SetControlError(nameof(MemberUserEmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!AccountMgr.UserAccountExistsByEmailAddress(MemberUserEmailAddress))
responseData.SetControlError(nameof(MemberUserEmailAddress), Localiser["msg.validation.invalid.value"]);
}
else
{
// User Account.
if (MemberUserAccountId != null)
{
SysId memberUserAccountId = new SysId(StringUtil.ToLong(MemberUserAccountId));
if (!SysId.IsValid(memberUserAccountId))
responseData.SetControlError(nameof(MemberUserAccountId), Localiser["msg.validation.mandatory.value"]);
}
}
// Member roles.
if (MemberRoleCodes != null)
{
// Roles can be left empty as the 'Member' role is automatically applied.
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,26 @@
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class Model_Policies : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_Policies(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,141 @@
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.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.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class Model_Region : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_Region(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_JS.Add("js/sax-region.js");
}
//-------------------------------------------------------------------
// List properties.
public class FilterData
{
public String Region { get; set; }
public String RegionTypeCode { get; set; }
}
public FilterData Filter { get; } = new FilterData();
public Sax.RegionType FilterRegionType
{
get { return (Sax.RegionType)RegionTypes.GetTypeForCode(Filter.RegionTypeCode); }
}
//-------------------------------------------------------------------
// Region properties.
public String RegionIdValue { get; set; }
public SysId RegionId
{
get { return new SysId(StringUtil.ToInt(RegionIdValue)); }
set { RegionIdValue = SysId.ToString(value); }
}
public String RegionTypeCode { get; set; }
public Sax.RegionType RegionType
{
get { return (Sax.RegionType)RegionTypes.GetTypeForCode(RegionTypeCode); }
set { RegionTypeCode = RegionTypes.GetCodeForType(value); }
}
public String RegionCountryIso2Code { get; set; }
public String RegionName { get; set; }
public String RegionStatusCode { get; set; }
public SaStatus RegionStatus { get { return SacsSystem.GetStatusForCode(RegionStatusCode); } }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Region.
public bool ValidateRegionData(SaxController_Base.AjaxResponse_Data responseData)
{
// Region Type.
if (RegionTypeCode != null)
{
responseData.SetControlValue(nameof(RegionTypeCode), RegionTypeCode);
if (StringUtil.IsEmpty(RegionTypeCode) || (int)RegionType < 1)
responseData.SetControlError(nameof(RegionTypeCode), Localiser["msg.validation.mandatory.value"]);
}
// Region ISO2 code.
if (RegionCountryIso2Code != null)
{
RegionCountryIso2Code = RegionCountryIso2Code.ToUpperInvariant();
responseData.SetControlValue(nameof(RegionCountryIso2Code), RegionCountryIso2Code);
if (RegionType == Sax.RegionType.Country)
{
if (StringUtil.IsEmpty(RegionCountryIso2Code))
responseData.SetControlError(nameof(RegionCountryIso2Code), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(RegionCountryIso2Code) != GetControlMaxLength(SaxDbDfn.ColumnIdx.Region_CountryIso2Code))
responseData.SetControlError(nameof(RegionCountryIso2Code), Localiser["msg.validation.invalid.length", GetControlMaxLength(SaxDbDfn.ColumnIdx.Region_CountryIso2Code)]);
else
{
// Check for duplicate code.
SaxRegion.Params prmRegion = new SaxRegion.Params();
if (SysId.IsValid(RegionId))
prmRegion.IdsExcluded.Add(RegionId);
prmRegion.CountryIso2CodesIncluded.Add(RegionCountryIso2Code);
prmRegion.StatusCat = SaxRegion.StatusCat.Any;
if (ReferenceMgr.GetRegionCount(prmRegion) > 0)
responseData.SetControlError(nameof(RegionCountryIso2Code), Localiser["msg.validation.duplicate.value"]);
}
}
else if ((int)RegionType > 0 && RegionType != Sax.RegionType.Country)
{
if (!StringUtil.IsEmpty(RegionCountryIso2Code))
responseData.SetControlError(nameof(RegionCountryIso2Code), Localiser["msg.validation.invalid.value"]);
}
}
// Region name.
if (RegionName != null)
{
responseData.SetControlValue(nameof(RegionName), RegionName);
if (StringUtil.IsEmpty(RegionName))
responseData.SetControlError(nameof(RegionName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(RegionName) > GetControlMaxLength(SaxDbDfn.ColumnIdx.Region_Name))
responseData.SetControlError(nameof(RegionName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn.ColumnIdx.Region_Name)]);
else
{
// Check for duplicate name.
SaxRegion.Params prmRegion = new SaxRegion.Params();
if (SysId.IsValid(RegionId))
prmRegion.IdsExcluded.Add(RegionId);
prmRegion.NameFull = RegionName;
prmRegion.StatusCat = SaxRegion.StatusCat.Any;
if (ReferenceMgr.GetRegionCount(prmRegion) > 0)
responseData.SetControlError(nameof(RegionName), Localiser["msg.validation.duplicate.value"]);
}
}
// Region status.
if (RegionStatusCode != null)
{
if (StringUtil.IsEmpty(RegionStatusCode))
responseData.SetControlError(nameof(RegionStatusCode), Localiser["msg.validation.mandatory.value"]);
else if (!SaxRegion.ValidStatuses.Contains(RegionStatus))
responseData.SetControlError(nameof(RegionStatusCode), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,113 @@
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
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.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class Model_Security : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_Security(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_CSS.Add("css/sax-security.css");
AdditionalFiles_JS.Add("js/sax-security.js");
}
//-------------------------------------------------------------------
// Page configuration.
public bool RequestPasswordMode { get { return !StringUtil.IsEmpty(rpe); } }
//-------------------------------------------------------------------
// Form properties.
public String EmailAddress { get; set; }
public String PasswordCurrent { get; set; }
public String Password { get; set; }
public String PasswordConfirm { get; set; }
public bool KeepMeSignedIn { get; set; }
/// <summary>Request Password Encrypted params - to allow Change Password without session or requiring old password.</summary>
public String rpe { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Sign-in.
public bool ValidateSignInData(SaxController_Base.AjaxResponse_Data responseData)
{
// Email address.
responseData.SetControlValue(nameof(EmailAddress), EmailAddress);
if (StringUtil.IsEmpty(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.invalid.value"]);
// Password.
// NB Do not return the password value in the response.
if (StringUtil.IsEmpty(Password))
responseData.SetControlError(nameof(Password), Localiser["msg.validation.mandatory.value"]);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Change password.
public bool ValidateChangePasswordData(SaxUserAccount userAccount, SaxController_Base.AjaxResponse_Data responseData)
{
// Validate Passwords.
// NB Do not return the password values in the response.
// NB Only validate populated values - any null values are not part of the form for this request.
// - 'Current' password is not shown when changing password from 'Request Password' email link.
if (PasswordCurrent != null && StringUtil.IsEmpty(PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["msg.validation.mandatory.value"]);
if (StringUtil.IsEmpty(Password))
responseData.SetControlError(nameof(Password), Localiser["msg.validation.mandatory.value"]);
if (StringUtil.IsEmpty(PasswordConfirm))
responseData.SetControlError(nameof(PasswordConfirm), Localiser["msg.validation.mandatory.value"]);
if (!StringUtil.IsEmpty(Password) && !StringUtil.IsEmpty(PasswordConfirm))
{
if (!Password.Equals(PasswordConfirm))
responseData.SetControlError(nameof(PasswordConfirm), Localiser["msg.validation.invalid.value"]);
if (!SaxPasswordHelper.IsValidPassword(Password))
responseData.SetControlError(nameof(PasswordConfirm), Localiser.GetText("msg.warn.password.invalid", new object[] { SaxPasswordHelper.Params.MinLength, SaxPasswordHelper.Params.MinLengthNoComplexity }));
}
if (!responseData.HasControlErrors())
{
if (PasswordCurrent != null && !SaxPasswordHelper.VerifyHashedPassword(userAccount, PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["security.change.password.current.invalid"]);
if (SaxPasswordHelper.VerifyHashedPassword(userAccount, Password))
responseData.SetControlError(nameof(Password), Localiser["security.change.password.not.changed"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Request Password.
public bool ValidateRequestPasswordData(SaxController_Base.AjaxResponse_Data responseData)
{
// Email address.
responseData.SetControlValue(nameof(EmailAddress), EmailAddress);
if (StringUtil.IsEmpty(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.invalid.value"]);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,91 @@
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.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.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class Model_Sport : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_Sport(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_JS.Add("js/sax-sport.js");
}
//-------------------------------------------------------------------
// Sport list configuration.
public class FilterData
{
public String Sport { get; set; }
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Sport properties.
public SysId SportId { get; set; }
public String SportIdValue
{
get { return SysId.ToString(SportId); }
set { SportId = new SysId(StringUtil.ToInt(value)); }
}
public String SportName { get; set; }
public String SportStatusCode { get; set; }
public SaStatus SportStatus { get { return SacsSystem.GetStatusForCode(SportStatusCode); } }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Sport.
public bool ValidateSportData(SaxController_Base.AjaxResponse_Data responseData)
{
// Sport name.
if (SportName != null)
{
responseData.SetControlValue(nameof(SportName), SportName);
if (StringUtil.IsEmpty(SportName))
responseData.SetControlError(nameof(SportName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(SportName) > GetControlMaxLength(SaxDbDfn.ColumnIdx.Sport_Name))
responseData.SetControlError(nameof(SportName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn.ColumnIdx.Sport_Name)]);
else
{
// Check for duplicate name.
SaxSport.Params prmSport = new SaxSport.Params();
if (SysId.IsValid(SportId))
prmSport.ExcludedIds.Add(SportId);
prmSport.NamePartial = SportName;
prmSport.StatusCat = SaxSport.StatusCat.Any;
if (ReferenceMgr.GetSportCount(prmSport) > 0)
responseData.SetControlError(nameof(SportName), Localiser["msg.validation.duplicate.value"]);
}
}
// Sport status.
if (SportStatusCode != null)
{
if (StringUtil.IsEmpty(SportStatusCode))
responseData.SetControlError(nameof(SportStatusCode), Localiser["msg.validation.mandatory.value"]);
else if (!SaxSport.ValidStatuses.Contains(SportStatus))
responseData.SetControlError(nameof(SportStatusCode), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,133 @@
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.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_User : SaxModel_Admin
{
//-------------------------------------------------------------------
// Construction.
public Model_User(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_JS.Add("js/sax-user.js");
}
//-------------------------------------------------------------------
// User list configuration.
public class FilterData
{
public String User { get; set; }
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// User properties.
public SysId UserAccountId { get; set; }
public String UserAccountIdValue
{
get { return SysId.ToString(UserAccountId); }
set { UserAccountId = new SysId(StringUtil.ToInt(value)); }
}
[EmailAddress]
public String UserEmailAddress { get; set; }
public String UserTitle { get; set; }
public String UserFirstName { get; set; }
public String UserLastName { get; set; }
public String UserRoleCodeDelim = ",";
public List<String> UserRoleCodes { get; set; }
public List<String> GetUserRoleCodes()
{
List<String> codes = new List<string>();
foreach (String code in UserRoleCodes)
codes.AddRange(code.Split(UserRoleCodeDelim));
return codes;
}
public String UserStatusCode { get; set; }
public SaStatus UserStatus { get { return SacsSystem.GetStatusForCode(UserStatusCode); } }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// User.
public bool ValidateUserData(SaxController_Base.AjaxResponse_Data responseData)
{
// User email.
if (UserEmailAddress != null)
{
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.ColumnIdx.UserAccount_EmailAddress))
responseData.SetControlError(nameof(UserEmailAddress), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_EmailAddress)]);
else
{
// Check for duplicate email address.
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
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)
{
if (StringUtil.IsEmpty(UserTitle))
responseData.SetControlError(nameof(UserTitle), Localiser["msg.validation.mandatory.value"]);
}
// 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.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(nameof(UserFirstName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn.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.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(nameof(UserLastName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_LastName)]);
}
// User status.
if (UserStatusCode != null)
{
if (StringUtil.IsEmpty(UserStatusCode))
responseData.SetControlError(nameof(UserStatusCode), Localiser["msg.validation.mandatory.value"]);
else if (!SaxUserAccount.ValidStatuses.Contains(UserStatus))
responseData.SetControlError(nameof(UserStatusCode), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,23 @@
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Models
{
//=======================================================================
public class SaxModel_Admin : SaxModel_Base
{
//-------------------------------------------------------------------
// Protected construction.
protected SaxModel_Admin(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,408 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="company.name" xml:space="preserve">
<value>Solum Forti</value>
</data>
<data name="copyright.symbol.year.html" xml:space="preserve">
<value>&amp;copy; {0}</value>
</data>
<data name="dialog.title.alert" xml:space="preserve">
<value>Alert</value>
</data>
<data name="dialog.title.confirm" xml:space="preserve">
<value>Confirm</value>
</data>
<data name="dialog.title.progress" xml:space="preserve">
<value>Progress</value>
</data>
<data name="dialog.title.signout" xml:space="preserve">
<value>Sign-out</value>
</data>
<data name="home.caption.application" xml:space="preserve">
<value>Admin</value>
</data>
<data name="home.caption.home" xml:space="preserve">
<value>Home</value>
</data>
<data name="label.accounts" xml:space="preserve">
<value>Accounts</value>
</data>
<data name="label.cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="label.category" xml:space="preserve">
<value>Category</value>
</data>
<data name="label.characters.count" xml:space="preserve">
<value>{0} characters</value>
</data>
<data name="label.characters.one" xml:space="preserve">
<value>1 character</value>
</data>
<data name="label.close" xml:space="preserve">
<value>Close</value>
</data>
<data name="label.create" xml:space="preserve">
<value>Create</value>
</data>
<data name="label.delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="label.email.address" xml:space="preserve">
<value>Email address</value>
</data>
<data name="label.first.name" xml:space="preserve">
<value>First name</value>
</data>
<data name="label.forgotten.password" xml:space="preserve">
<value>Forgotten password?</value>
</data>
<data name="label.iso2.code" xml:space="preserve">
<value>ISO2 code</value>
</data>
<data name="label.keep.signed.in" xml:space="preserve">
<value>Keep me signed-in</value>
</data>
<data name="label.last.name" xml:space="preserve">
<value>Last name</value>
</data>
<data name="label.members" xml:space="preserve">
<value>Members</value>
</data>
<data name="label.name" xml:space="preserve">
<value>Name</value>
</data>
<data name="label.no" xml:space="preserve">
<value>No</value>
</data>
<data name="label.ok" xml:space="preserve">
<value>OK</value>
</data>
<data name="label.password" xml:space="preserve">
<value>Password</value>
</data>
<data name="label.password.confirm" xml:space="preserve">
<value>Confirm password</value>
</data>
<data name="label.password.current" xml:space="preserve">
<value>Current password</value>
</data>
<data name="label.reference" xml:space="preserve">
<value>Reference</value>
</data>
<data name="label.refresh" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="label.remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="label.roles" xml:space="preserve">
<value>Roles</value>
</data>
<data name="label.security" xml:space="preserve">
<value>Security</value>
</data>
<data name="label.select" xml:space="preserve">
<value>Select</value>
</data>
<data name="label.selected.count" xml:space="preserve">
<value>{0} selected</value>
</data>
<data name="label.selected.none" xml:space="preserve">
<value>none selected</value>
</data>
<data name="label.strength" xml:space="preserve">
<value>Strength</value>
</data>
<data name="label.title" xml:space="preserve">
<value>Title</value>
</data>
<data name="label.type" xml:space="preserve">
<value>Type</value>
</data>
<data name="label.update" xml:space="preserve">
<value>update</value>
</data>
<data name="label.user.roles" xml:space="preserve">
<value>User roles</value>
</data>
<data name="label.yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="msg.data.noneFound" xml:space="preserve">
<value>No matching data has been found.</value>
</data>
<data name="msg.data.querying" xml:space="preserve">
<value>Querying data. Please wait ...</value>
</data>
<data name="msg.data.refreshing" xml:space="preserve">
<value>Refreshing data. Please wait ...</value>
</data>
<data name="msg.error.data.incomplete" xml:space="preserve">
<value>Data is incomplete</value>
</data>
<data name="msg.error.item.delete.failed" xml:space="preserve">
<value>Item deletion failed</value>
</data>
<data name="msg.error.item.id.not.found" xml:space="preserve">
<value>Item not found for specified Id</value>
</data>
<data name="msg.error.item.id.not.specified" xml:space="preserve">
<value>Item Id not specified</value>
</data>
<data name="msg.error.item.insert.failed" xml:space="preserve">
<value>Item creation failed</value>
</data>
<data name="msg.error.item.update.failed" xml:space="preserve">
<value>Item update failed</value>
</data>
<data name="msg.error.system" xml:space="preserve">
<value>System error encountered.</value>
</data>
<data name="msg.something.gone.wrong" xml:space="preserve">
<value>Unfortunately, something has gone wrong.\n\nYou will be returned to the home page.</value>
</data>
<data name="msg.validation.duplicate.value" xml:space="preserve">
<value>Value is already in use</value>
</data>
<data name="msg.validation.invalid.length" xml:space="preserve">
<value>Value must be {0} characters</value>
</data>
<data name="msg.validation.invalid.value" xml:space="preserve">
<value>Invalid value</value>
</data>
<data name="msg.validation.mandatory.value" xml:space="preserve">
<value>Mandatory value</value>
</data>
<data name="msg.validation.maximum.length" xml:space="preserve">
<value>Value must not exceed {0} characters</value>
</data>
<data name="organisation.caption.member" xml:space="preserve">
<value>Organisation Member</value>
</data>
<data name="organisation.caption.member.create" xml:space="preserve">
<value>Add Member</value>
</data>
<data name="organisation.caption.member.remove" xml:space="preserve">
<value>Remove Member</value>
</data>
<data name="organisation.caption.member.update" xml:space="preserve">
<value>Update Member</value>
</data>
<data name="organisation.caption.members" xml:space="preserve">
<value>Organisation Members</value>
</data>
<data name="organisation.caption.organisation" xml:space="preserve">
<value>Organisation</value>
</data>
<data name="organisation.caption.organisation.create" xml:space="preserve">
<value>Create Organisation</value>
</data>
<data name="organisation.caption.organisation.update" xml:space="preserve">
<value>Update Organisation</value>
</data>
<data name="organisation.caption.organisations" xml:space="preserve">
<value>Organisations</value>
</data>
<data name="organisation.caption.select.member.roles" xml:space="preserve">
<value>Select Member Roles</value>
</data>
<data name="organisation.caption.select.organisation" xml:space="preserve">
<value>Select Organisation</value>
</data>
<data name="policies.caption.privacy" xml:space="preserve">
<value>Privacy</value>
</data>
<data name="region.caption.region" xml:space="preserve">
<value>Region</value>
</data>
<data name="region.caption.region.create" xml:space="preserve">
<value>Create Region</value>
</data>
<data name="region.caption.region.update" xml:space="preserve">
<value>Update Region</value>
</data>
<data name="region.caption.regions" xml:space="preserve">
<value>Regions</value>
</data>
<data name="security.caption.change.password" xml:space="preserve">
<value>Change Password</value>
</data>
<data name="security.caption.request.password" xml:space="preserve">
<value>Request Password</value>
</data>
<data name="security.caption.signin" xml:space="preserve">
<value>Sign-in</value>
</data>
<data name="security.caption.signout" xml:space="preserve">
<value>Sign-out</value>
</data>
<data name="security.change.password.current.invalid" xml:space="preserve">
<value>Current password does not match.</value>
</data>
<data name="security.change.password.not.changed" xml:space="preserve">
<value>Your new password must be different from the previous.</value>
</data>
<data name="security.msg.change.password.failed" xml:space="preserve">
<value>Failed to change password.</value>
</data>
<data name="security.msg.request.password.email.sent" xml:space="preserve">
<value>If your email address matches an active user account, you will shortly receive an email with instructions for changing your password.</value>
</data>
<data name="security.msg.request.password.saved" xml:space="preserve">
<value>Password has been updated</value>
</data>
<data name="security.msg.signin.invalid" xml:space="preserve">
<value>Incorrect account credentials entered.</value>
</data>
<data name="security.msg.signout.confirm" xml:space="preserve">
<value>Are you sure that you want to sign-out?</value>
</data>
<data name="security.msg.warn.sessionExpired" xml:space="preserve">
<value>Your session has expired.</value>
</data>
<data name="sport.caption.sport" xml:space="preserve">
<value>Sport</value>
</data>
<data name="sport.caption.sport.create" xml:space="preserve">
<value>Create Sport</value>
</data>
<data name="sport.caption.sport.update" xml:space="preserve">
<value>Update Sport</value>
</data>
<data name="sport.caption.sports" xml:space="preserve">
<value>Sports</value>
</data>
<data name="user.caption.select.user.roles" xml:space="preserve">
<value>Select User Roles</value>
</data>
<data name="user.caption.user" xml:space="preserve">
<value>User</value>
</data>
<data name="user.caption.user.create" xml:space="preserve">
<value>Create User</value>
</data>
<data name="user.caption.user.update" xml:space="preserve">
<value>Update User</value>
</data>
<data name="user.caption.users" xml:space="preserve">
<value>Users</value>
</data>
</root>
@@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50421",
"sslPort": 44351
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
},
"SaxoniteCoreWebAdmin": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
}
}
}
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
<RootNamespace>Saxonite.Net.Core.Web</RootNamespace>
<AssemblyName>Saxonite.Net.Core.Web</AssemblyName>
<UserSecretsId>9d0d73e2-7295-4e06-a75d-9989fc0ceacb</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="jQuery.UI.Combined" Version="1.12.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.13" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
<ProjectReference Include="..\SaxoniteCoreWeb\SaxoniteCoreWeb.csproj" />
<ProjectReference Include="..\SaxoniteStandardBase\SaxoniteStandardBase.csproj" />
<ProjectReference Include="..\SaxoniteStandardDbas\SaxoniteStandardDbas.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localisation\SaxAdminProperties.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
</Project>
@@ -0,0 +1,60 @@
@model SaxModel_Admin
@inject SaxLocaliser Localiser
<link rel="stylesheet" href="~/css/sax-codeSelection.css?@Model.HrefVersion" />
<script src="~/js/sax-codeSelection.js?@Model.HrefVersion"></script>
<script type="text/javascript">
</script>
<div id="dlgCodeSelection" class="sax-dialog sax-form">
@* Filter *@
<div class="">
</div>
<div class="row h-100">
@* Available codes *@
<div class="col-sm-5">
<div class="h-100 sax-codeSelection-codes AvailableCodes">
</div>
</div>
@* Code selection *@
<div class="col-sm-2">
<div class="row">
<div class="col-sx-6 col-sm-12 center">
<button class="sax-button" onclick="">@Localiser["label.select"]</button>
</div>
<div class="col-sx-6 col-sm-12 center">
<button class="sax-button" onclick="">@Localiser["label.remove"]</button>
</div>
</div>
</div>
@* Selected codes *@
<div class="col-sm-5">
<div class="h-100 sax-codeSelection-codes SelectedCodes">
</div>
</div>
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Code">
<div class="CodeDesc"></div>
</div>
</div>
</div>
@@ -0,0 +1,42 @@
@model SaxModel_Admin
@inject SaxLocaliser Localiser
<link rel="stylesheet" href="~/css/sax-organisationSelection.css?@Model.HrefVersion" />
<script src="~/js/sax-organisationSelection.js?@Model.HrefVersion"></script>
<script type="text/javascript">
sax.organisationSelection.dlgTitleSelectOrganisation = '@Localiser.GetJavascriptText("organisation.caption.select.organisation")';
</script>
<div id="dlgOrganisationSelection" class="sax-dialog sax-form">
@* Filter *@
<div class="sax-panel-filter">
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationSelectionFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-5 sax-label text-left text-sm-right">@Localiser["organisation.caption.organisation"]</div>
<div class="col-sm-7"><input type="text" id="" class="sax-input" name="Filter.Organisation" onkeypress="sax.organisationSelection.fn.refreshOrganisations(250)" /></div>
</div>
}
</div>
</div>
<div class="">
@* Organisations *@
<div class="sax-selection-results Organisations"></div>
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Organisation">
<div class="Name"></div>
</div>
</div>
</div>
@@ -0,0 +1,8 @@
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
</div>
@@ -0,0 +1,57 @@
@model Model_Organisation
@inject SaxLocaliser Localiser
@{
/* Organisations - Index */
ViewData["Title"] = "Organisations";
}
<div class="">
<div class="sax-panel-filter">
<div class="sax-header">
<div class="floatl sax-caption">@Localiser["organisation.caption.organisations"]</div>
<div class="floatr sax-action sax-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
<div class="floatr sax-action sax-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-5 sax-label text-left text-sm-right">@Localiser["organisation.caption.organisation"]</div>
<div class="col-sm-7">@Html.TextBoxFor(model => model.Filter.Organisation, new { @id = "ifFilterOrganisation", @class = "FilterOrganisation", @maxlength = "32", @onkeypress = "sax.refreshResults(250)" })</div>
</div>
}
</div>
</div>
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Organisation">
<div class="sax-header">
<div class="floatl sax-caption"><div class="OrganisationName">Organisation name</div></div>
<div class="floatr sax-action sax-item-update"><img src="~/img/action/edit24.png" alt="@(Localiser["label.update"])" title="@(Localiser["label.update"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-4"><div class="OrganisationCategory"></div></div>
<div class="col-sm-4"><div class="OrganisationType"></div></div>
<div class="col-sm-4 right"><div class=""></div></div>
</div>
<div class="row">
<div class="col-sm-4"><div class="OrganisationMemberCount sax-action-text">@Localiser["label.members"]:&nbsp;<span class="MemberCount"></span></div></div>
<div class="col-sm-4"><div class=""></div></div>
<div class="col-sm-4 right"><div class="StatusDesc"></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<partial name="Panel-OrganisationDialog.cshtml" />
@@ -0,0 +1,65 @@
@model Model_OrganisationMembers
@inject SaxLocaliser Localiser
@{
/* Organisation Members - Index */
ViewData["Title"] = "Organisation Members";
}
<div class="">
<div class="sax-panel-filter OrganisationMembers">
<div class="sax-header">
<div class="floatl sax-caption">@Localiser["organisation.caption.members"]</div>
<div class="floatr sax-action sax-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
<div class="floatr sax-action sax-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmMemberFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-3 sax-label text-left text-sm-right">@Localiser["organisation.caption.organisation"]</div>
<div class="col-sm-9">
<div class="row">
<div class="col">
@Html.HiddenFor(model => model.Filter.OrganisationId, new { @id = "ifFilterOrganisationId" })
<input type="text" id="ifFilterOrganisation" class="sax-input" readonly="readonly" value="@(Model.MemberOrganisationName)" />
</div>
<div class="fixed"><button id="" class="sax-button" onclick="sax.selectOrganisation()">@Localiser["label.select"]</button></div>
</div>
</div>
</div>
}
</div>
</div>
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Member">
<div class="sax-header">
<div class="floatl sax-caption"><div class="UserDisplayName"></div></div>
<div class="floatr sax-action sax-item-update"><img src="~/img/action/edit24.png" alt="@(Localiser["label.update"])" title="@(Localiser["label.update"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-6"><div class="UserEmailAddress"></div></div>
<div class="col-sm-6 right"><div class="MemberRoleCount">@Localiser["label.roles"]:&nbsp;<span class="RoleCount"></span></div></div>
</div>
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-4"><div class=""></div></div>
<div class="col-sm-4 right"><div class="StatusDesc"></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<partial name="Panel-MemberDialog.cshtml" />
<partial name="~/Views/Common/OrganisationSelection.cshtml" />
@@ -0,0 +1,65 @@
@model Model_OrganisationMembers
@inject SaxLocaliser Localiser
@{
/* Organisations - Organisation dialog panel */
}
<script type="text/javascript">
sax.dlg.title.selectOrganisation = '@Localiser.GetJavascriptText("organisation.caption.select.organisation")';
sax.dlg.title.createMember = '@Localiser.GetJavascriptText("organisation.caption.member.create")';
sax.dlg.title.updateMember = '@Localiser.GetJavascriptText("organisation.caption.member.update")';
sax.dlg.title.removeMember = '@Localiser.GetJavascriptText("organisation.caption.member.remove")';
sax.dlg.title.selectMemberRoles = '@Localiser.GetJavascriptText("organisation.caption.select.member.roles")';
sax.data.userRoleCodeDelim = '@Model.MemberRoleCodeDelim';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmMemberId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.UserOrganisationId, new { @id = "", @class = "UserOrganisationId" })
}
<div id="dlgMember" class="sax-dialog" title="">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmMember", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.UserOrganisationId, new { @id = "", @class = "UserOrganisationId" })
@Html.HiddenFor(model => model.MemberOrganisationId, new { @id = "", @class = "MemberOrganisationId" })
@* User email *@
<div class="sax-form-control UserEmailAddress">
<div class="sax-label">
<label for="ifMemberUserEmailAddress"><span><span class="required">*</span> @Localiser["label.email.address"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.MemberUserEmailAddress, new { @id = "ifMemberUserEmailAddress", @class = "MemberUserEmailAddress sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sax-message"></div>
</div>
@* User email *@
<div class="sax-form-control UserName">
<div class="sax-label">
<label for="ifMemberUserName"><span>@Localiser["label.name"]</span></label>
</div>
<div class="sax-control">
<input type="text" id="ifMemberUserName" class="MemberUserName sax-input" readonly="readonly" />
</div>
<div class="sax-message"></div>
</div>
@* Member roles *@
<div class="sax-form-control MemberRoleCodes">
<div class="sax-label">
<label for="ifMemberRoles"><span>@Localiser["label.roles"]</span></label>
</div>
<div class="sax-control row">
@Html.HiddenFor(model => model.MemberRoleCodes, new { @id = "ifMemberRoleCodes", @class = "MemberRoleCodes sax-input" })
<div class="col"><textarea id="ifMemberRoles" class="MemberRoles sax-input" rows="6" readonly="readonly" ondblclick="sax.selectMemberRoles()"></textarea></div>
<div class="fixed"><button id="" class="sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px" onclick="sax.selectMemberRoles()">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
}
<partial name="~/Views/Common/CodeSelection.cshtml" />
</div>
@@ -0,0 +1,58 @@
@model Model_Organisation
@inject SaxLocaliser Localiser
@{
/* Organisations - Organisation dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createOrganisation = '@Localiser.GetJavascriptText("organisation.caption.organisation.create")';
sax.dlg.title.updateOrganisation = '@Localiser.GetJavascriptText("organisation.caption.organisation.update")';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.OrganisationId, new { @class = "OrganisationId" })
}
<div id="dlgOrganisation" class="sax-dialog" title="">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisation", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.OrganisationId, new { @id="", @class = "OrganisationId" })
@* Organisation name *@
<div class="sax-form-control OrganisationName">
<div class="sax-label">
<label for="ifOrganisationName"><span><span class="required">*</span> @Localiser["label.name"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.OrganisationName, new { @id = "ifOrganisationName", @class = "OrganisationName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.Organisation_Name) })
</div>
<div class="sax-message"></div>
</div>
@* Organisation type *@
<div class="sax-form-control OrganisationType">
<div class="sax-label">
<label for="cbOrganisationType"><span><span class="required">*</span> @Localiser["label.type"]</span></label>
</div>
<div class="sax-control">
@Html.DropDownListFor(model => model.OrganisationTypeValue, Model.OrganisationTypesForSelect, Localiser["label.select"], new { @id = "cbOrganisationType", @class = "OrganisationType sax-select" })
</div>
<div class="sax-message"></div>
</div>
@* Organisation category *@
<div class="sax-form-control OrganisationCategory">
<div class="sax-label">
<label for="cbOrganisationCategory"><span><span class="required">*</span> @Localiser["label.category"]</span></label>
</div>
<div class="sax-control">
@Html.DropDownListFor(model => model.OrganisationCategoryValue, Model.OrganisationCategoriesForSelect, Localiser["label.select"], new { @id = "cbOrganisationCategory", @class = "OrganisationCategory sax-select" })
</div>
<div class="sax-message"></div>
</div>
@* Organisation country *@
}
</div>
@@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
@@ -0,0 +1,56 @@
@model Model_Region
@inject SaxLocaliser Localiser
@{
/* Regions - Index */
ViewData["Title"] = "Regions";
}
<div class="">
<div class="sax-panel-filter">
<div class="sax-header">
<div class="floatl sax-caption">@Localiser["region.caption.regions"]</div>
<div class="floatr sax-action sax-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
<div class="floatr sax-action sax-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRegionFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-5 sax-label text-left text-sm-right">@Localiser["region.caption.region"]</div>
<div class="col-sm-7">@Html.TextBoxFor(model => model.Filter.Region, new { @id = "ifFilterRegion", @class = "FilterRegion", @maxlength = "32", @onkeypress = "sax.refreshResults(250)" })</div>
</div>
}
</div>
</div>
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Region">
<div class="sax-header">
<div class="floatl sax-caption"><span class="RegionName"></span> <span class="RegionCountryIso2Code"></span></div>
<div class="floatr sax-action sax-item-update"><img src="~/img/action/edit24.png" alt="@(Localiser["label.update"])" title="@(Localiser["label.update"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-4"><div class="RegionTypeDesc"></div></div>
<div class="col-sm-4"><div class="RegionParentName"></div></div>
<div class="col-sm-4 right"><div class="RegionStatusDesc"></div></div>
</div>
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-4"><div class=""></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<partial name="Panel-RegionDialog.cshtml" />
@@ -0,0 +1,56 @@
@model Model_Region
@inject SaxLocaliser Localiser
@{
/* Region form dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createRegion = '@Localiser.GetJavascriptText("region.caption.region.create")';
sax.dlg.title.updateRegion = '@Localiser.GetJavascriptText("region.caption.region.update")';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRegionId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.RegionIdValue, new { @class = "RegionId" })
}
<div id="dlgRegion" class="sax-dialog" title="">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRegion", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.RegionIdValue, new { @class = "RegionId" })
@* Region type *@
<div class="sax-form-control RegionType">
<div class="sax-label">
<label for="cbRegionType"><span><span class="required">*</span> @Localiser["label.type"]</span></label>
</div>
<div class="sax-control">
@Html.DropDownListFor(model => model.RegionTypeCode, Model.RegionTypesForSelect, Localiser["label.select"], new { @id = "cbRegionType", @class = "RegionType sax-select" })
</div>
<div class="sax-message"></div>
</div>
@* Country ISO2 code *@
<div class="sax-form-control RegionIso2Code">
<div class="sax-label">
<label for="ifRegionIso2Code"><span><span class="required">*</span> @Localiser["label.iso2.code"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.RegionCountryIso2Code, new { @id = "ifRegionCountryIso2Code", @class = "RegionCountryIso2Code sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.Region_CountryIso2Code) })
</div>
<div class="sax-message"></div>
</div>
@* Region name *@
<div class="sax-form-control RegionName">
<div class="sax-label">
<label for="ifRegionName"><span><span class="required">*</span> @Localiser["label.name"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.RegionName, new { @id = "ifRegionName", @class = "RegionName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.Region_Name) })
</div>
<div class="sax-message"></div>
</div>
}
</div>
@@ -0,0 +1,63 @@
@model Model_Security
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Change Password";
}
<div class="">
<div class="sax-panel-form security">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmChangePassword", @class = "sax-form" }))
{
<div class="sax-heading"><div class="sax-caption">@Localiser["security.caption.change.password"]</div></div>
@if (!Model.RequestPasswordMode)
{
/* Current password - required when setting password within user session. */
<div class="sax-form-control">
<div class="sax-label"><label for="ifPasswordCurrent"><span><span class="required">*</span> @Localiser["label.password.current"]</span></label></div>
<div class="sax-control">
@Html.PasswordFor(model => model.PasswordCurrent, new { @id = "ifPasswordCurrent", @class = "sax-input", @maxlength = Model.PasswordMaxLength })
</div>
<div class="sax-message"></div>
</div>
}
else
{
/* 'Request Password' encrypted parameters from email link. */
@Html.HiddenFor(model => model.rpe, new { @id = "ifRpe" });
}
<div class="spacer4"></div>
<div class="sax-form-control">
<div class="sax-label"><label for="ifPassword"><span><span class="required">*</span> @Localiser["label.password"]</span></label></div>
<div class="sax-control">
@Html.PasswordFor(model => model.Password, new { @id = "ifPassword", @class = "sax-input", @maxlength = Model.PasswordMaxLength })
</div>
<div class="row">
<div class="col-auto sax-label-text pt4">@Localiser["label.strength"]&nbsp;</div>
<div id="" class="col-auto"><div id="prgPasswordStrength" class="col-auto"></div></div>
<div class="clearb"></div>
</div>
<div class="sax-message"></div>
</div>
<div class="sax-form-control">
<div class="sax-label"><label for="ifPasswordConfirm"><span><span class="required">*</span> @Localiser["label.password.confirm"]</span></label></div>
<div class="sax-control">
@Html.PasswordFor(model => model.PasswordConfirm, new { @id = "ifPasswordConfirm", @class = "sax-input", @maxlength = Model.PasswordMaxLength })
</div>
<div class="sax-message"></div>
</div>
<div class="spacer4"></div>
<div class="">
<div id="pbChangePassword" class="sax-button-action fullwidth pointer" style="margin:16px 0;">@Localiser["security.caption.change.password"]</div>
</div>
}
</div>
</div>
@@ -0,0 +1,32 @@
@model Model_Security
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Request Password";
}
<div class="">
<div class="sax-panel-form security">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRequestPassword", @class = "sax-form" }))
{
<div class="sax-heading"><div class="sax-caption">@Localiser["security.caption.request.password"]</div></div>
<div class="sax-form-control">
<div class="sax-label">
<label for="ifEmailAddress"><span><span class="required">*</span> @Localiser["label.email.address"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.EmailAddress, new { @id = "ifEmailAddress", @class = "sax-input lower", @maxlength = "128" })
</div>
<div class="sax-message"></div>
</div>
<div class="spacer4"></div>
<div class="">
<div id="pbRequestPassword" class="sax-button-action fullwidth pointer" style="margin:16px 0;">@Localiser["security.caption.request.password"]</div>
</div>
}
</div>
</div>
@@ -0,0 +1,61 @@
@model Model_Security
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Signin";
}
<div class="">
<div class="sax-panel-form security">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSignIn", @class = "sax-form" }))
{
<div class="sax-heading"><div class="sax-caption">@Localiser["security.caption.signin"]</div></div>
<div class="sax-form-control">
<div class="sax-label">
<label for="ifEmailAddress"><span><span class="required">*</span> @Localiser["label.email.address"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.EmailAddress, new { @id = "ifEmailAddress", @class = "sax-input lower", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sax-message"></div>
</div>
<div class="sax-form-control">
<div class="row">
<div class="col-6">
<div class="sax-label">
<label for="ifPassword"><span><span class="required">*</span> @Localiser["label.password"]</span></label>
</div>
</div>
<div class="col-6 right">
<div class="sax-action-link" style="font-size:12px;" onclick="sax.goToPage('Security/RequestPassword');">@Localiser["label.forgotten.password"]</div>
</div>
</div>
<div class="clearb"></div>
<div class="sax-control">
@Html.PasswordFor(model => model.Password, new { @id = "ifPassword", @class = "sax-input", @maxlength = @Model.PasswordMaxLength })
</div>
<div class="sax-message"></div>
</div>
<div class="spacer4"></div>
<div class="sax-form-control">
<div class="">
@Html.CheckBoxFor(model => model.KeepMeSignedIn, new { @id = "chkKeepMeSignedIn" })
&nbsp;
<span class="sax-label pointer" onclick="$('#chkKeepMeSignedIn').click()">@Localiser["label.keep.signed.in"]</span>
</div>
</div>
<div class="">
<div id="pbSignIn" class="sax-button-action fullwidth pointer" style="margin:16px 0;">@Localiser["security.caption.signin"]</div>
</div>
}
</div>
</div>
@@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@@ -0,0 +1,25 @@
@using Microsoft.AspNetCore.Http.Features
@{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}
@if (showBanner)
{
<div id="cookieConsent" class="alert alert-info alert-dismissible fade show" role="alert">
Use this space to summarize your privacy and cookie use policy. <a asp-area="" asp-controller="Policies" asp-action="Privacy">Learn More</a>.
<button type="button" class="accept-policy close" data-dismiss="alert" aria-label="Close" data-cookie-string="@cookieString">
<span aria-hidden="true">Accept</span>
</button>
</div>
<script>
(function () {
var button = document.querySelector("#cookieConsent button[data-cookie-string]");
button.addEventListener("click", function (event) {
document.cookie = button.dataset.cookieString;
}, false);
})();
</script>
}
@@ -0,0 +1 @@
<div>Google Analytics</div>
@@ -0,0 +1,213 @@
@model SaxModel_Admin
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery AntiForgery;
@inject SaxLocaliser Localiser
@*
Application: Online
Purpose: Page template.
*@
@{
// Model properties to become javascript values.
var awaitingLogin = Model.AwaitingLogin ? "1" : "0";
var keepLoggedIn = Model.KeepLoggedIn ? "1" : "0";
var debug = Model.DebugMode ? "1" : "0";
}
<!DOCTYPE html>
<html lang="en">
<head>
@* Set base URL the same for all pages so that relative URLs work the same for all. *@
<base href="@Model.RedirectLocation.Base" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@* IE11 incompatibility eg. 'border-radius' not recognised. Must be at top of head *@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
@if (Model.ProhibitCache)
{
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Script-Type" content="application/javascript" />
<title>@ViewData["Title"]</title>
@* Stylesheets *@
<link rel="stylesheet" href="~/lib/jquery-ui/jquery-ui.css" />
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
crossorigin="anonymous"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" />
</environment>
<link rel="stylesheet" href="~/css/sax-base.css?@Model.HrefVersion" />
<link rel="stylesheet" href="~/css/sax-main.css?@Model.HrefVersion" />
<link rel="stylesheet" href="~/css/sax-data.css?@Model.HrefVersion" />
<link rel="stylesheet" href="~/css/sax-form.css?@Model.HrefVersion" />
@foreach (var cssFile in Model.AdditionalFiles_CSS)
{
<link rel="stylesheet" href="~/@cssFile?@Model.HrefVersion" />
}
@* Scripts *@
<script>
var sax = sax || { page: {}, url: {}, links: {}, form: {}, data: {}, msg: {}, lbl: {}, dlg: { title: {}, btn: {} }, $frm: {}, $dlg: {} };
sax.url.base = '@Model.RedirectLocation.Base';
sax.url.home = '@Model.RedirectLocation.Home';
sax.url.sessionExpired = '@Model.RedirectLocation.Home_SessionExpired';
sax.links.facebook = '@Model.Links.Facebook';
sax.links.linkedin = '@Model.Links.LinkedIn';
sax.dlg.title.alert = '@Localiser.GetJavascriptText("dialog.title.alert")';
sax.dlg.title.confirm = '@Localiser.GetJavascriptText("dialog.title.confirm")';
sax.dlg.title.progress = '@Localiser.GetJavascriptText("dialog.title.progress")';
sax.dlg.title.signOut = '@Localiser.GetJavascriptText("dialog.title.signout")';
sax.dlg.btn.yes = '@Localiser["label.yes"]';
sax.dlg.btn.no = '@Localiser["label.no"]';
sax.dlg.btn.ok = '@Localiser["label.ok"]';
sax.dlg.btn.close = '@Localiser["label.close"]';
sax.dlg.btn.cancel = '@Localiser["label.cancel"]';
sax.lbl.availableCharacter = '@Localiser.GetJavascriptText("label.characters.one")';
sax.lbl.availableCharacters = '@Localiser.GetJavascriptText("label.characters.count", "{0}")';
sax.lbl.noneSelected = '@Localiser.GetJavascriptText("label.selected.none")';
sax.lbl.numberSelected = '@Localiser.GetJavascriptText("label.selected.count", "{0}")';
sax.msg.signOutConfirm = '@Localiser.GetJavascriptText("security.msg.signout.confirm")';
sax.msg.somethingGoneWrong = '@Localiser.GetJavascriptText("msg.something.gone.wrong")';
sax.msg.dataRefreshing = '@Localiser.GetJavascriptText("msg.data.refreshing")';
sax.msg.dataQuerying = '@Localiser.GetJavascriptText("msg.data.querying")';
sax.msg.dataNoneFound = '@Localiser.GetJavascriptText("msg.data.noneFound")';
sax.sessionCookieName = '@Model.SessionCookieName';
sax.awaitingLogin = @awaitingLogin;
sax.keepLoggedIn = @keepLoggedIn;
sax.keepLoggedInDelay = @(Sax.Config.Admin.Session.Cookie.KeepLoggedInTimeoutMillis);
sax.debugMode = @debug;
</script>
</head>
<body>
@* Header *@
<header>
@* Navigation bar *@
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">@Localiser["home.caption.application"]</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">@Localiser["home.caption.home"]</a>
</li>
@if (Model.AwaitingLogin)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Policies" asp-action="Privacy">@Localiser["policies.caption.privacy"]</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Security" asp-action="">@Localiser["security.caption.signin"]</a>
</li>
}
else
{
<li class="nav-item dropdown">
<a class="nav-link text-dark dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@Localiser["label.reference"]
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sax-action" onclick="sax.goToPage('Region');">@Localiser["region.caption.regions"]</div>
<div class="dropdown-item text-dark sax-action" onclick="sax.goToPage('Sport');">@Localiser["sport.caption.sports"]</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link text-dark dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@Localiser["label.accounts"]
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sax-action" onclick="sax.goToPage('User');">@Localiser["user.caption.users"]</div>
<div class="dropdown-item text-dark sax-action" onclick="sax.goToPage('Organisation');">@Localiser["organisation.caption.organisations"]</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link text-dark dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@Localiser["label.security"]
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sax-action" onclick="sax.navigateTo('Security/ChangePassword');">@Localiser["security.caption.change.password"]</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item text-dark sax-action" onclick="sax.onSignOut();">@Localiser["security.caption.signout"]</div>
</div>
</li>
}
</ul>
</div>
</div>
</nav>
@* Page caption *@
@if (!StringUtil.IsEmpty(Model.PageCaption))
{
<div class="container">
<div class="sax-page-caption">@(Model.PageCaption)</div>
</div>
}
</header>
@* Page body *@
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
@* Footer *@
<footer class="border-top footer text-muted">
<div class="container">
@(Model.CopyrightNotice) - @Localiser["company.name"] @(Model.InstanceVersion) - <a asp-area="" asp-controller="Home" asp-action="">Home</a> - <a asp-area="" asp-controller="Policies" asp-action="Privacy">Privacy</a>
</div>
</footer>
@* Form to allow page navigation. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmNavigate" })) { }
<div id='sax-please-wait'></div>
@* Scripts *@
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-xrRywqdh3PHs8keKZN+8zzc5TX0GRTLCcmivcbNJWm2rs5C8PRhcEn3czEjhAO9o">
</script>
</environment>
<script src="~/lib/jquery-ui/jquery-ui.js"></script>
<script src="~/js/sax-main.js?@Model.HrefVersion"></script>
@foreach (var jsFile in Model.AdditionalFiles_JS)
{
<script src="~/@jsFile?@Model.HrefVersion"></script>
}
@if (Model.IncludeGoogleAnalytics)
{
<partial name="~/Views/Shared/_GoogleAnalytics.cshtml" />
}
@RenderSection("Scripts", required: false)
</body>
</html>
@@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@@ -0,0 +1,56 @@
@model Model_Sport
@inject SaxLocaliser Localiser
@{
/* Sports - Index */
ViewData["Title"] = "Sports";
}
<div class="">
<div class="sax-panel-filter">
<div class="sax-header">
<div class="floatl sax-caption">@Localiser["sport.caption.sports"]</div>
<div class="floatr sax-action sax-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
<div class="floatr sax-action sax-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSportFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-5 sax-label text-left text-sm-right">@Localiser["sport.caption.sport"]</div>
<div class="col-sm-7">@Html.TextBoxFor(model => model.Filter.Sport, new { @id = "ifFilterSport", @class = "FilterSport", @maxlength = "32", @onkeypress = "sax.refreshResults(250)" })</div>
</div>
}
</div>
</div>
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Sport">
<div class="sax-header">
<div class="floatl sax-caption"><div class="SportName">Sport name</div></div>
<div class="floatr sax-action sax-item-update"><img src="~/img/action/edit24.png" alt="@(Localiser["label.update"])" title="@(Localiser["label.update"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-4"><div class="SportCategory"></div></div>
<div class="col-sm-4"><div class="SportType"></div></div>
<div class="col-sm-4 right"><div class="StatusDesc"></div></div>
</div>
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-4"><div class=""></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<partial name="Panel-SportDialog.cshtml" />
@@ -0,0 +1,34 @@
@model Model_Sport
@inject SaxLocaliser Localiser
@{
/* Sports - Sport dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createSport = '@Localiser.GetJavascriptText("sport.caption.sport.create")';
sax.dlg.title.updateSport = '@Localiser.GetJavascriptText("sport.caption.sport.update")';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSportId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.SportIdValue, new { @class = "SportId" })
}
<div id="dlgSport" class="sax-dialog" title="">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSport", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.SportIdValue, new { @class = "SportId" })
@* Sport name *@
<div class="sax-form-control SportName">
<div class="sax-label">
<label for="ifSportName"><span><span class="required">*</span> @Localiser["label.name"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.SportName, new { @id = "ifSportName", @class = "SportName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.Sport_Name) })
</div>
<div class="sax-message"></div>
</div>
}
</div>
@@ -0,0 +1,56 @@
@model Model_User
@inject SaxLocaliser Localiser
@{
/* Users - Index */
ViewData["Title"] = "Users";
}
<div class="">
<div class="sax-panel-filter">
<div class="sax-header">
<div class="floatl sax-caption">@Localiser["user.caption.users"]</div>
<div class="floatr sax-action sax-results-refresh"><img src="~/img/action/refresh24.png" alt="@(Localiser["label.refresh"])" title="@(Localiser["label.refresh"])" /></div>
<div class="floatr sax-action sax-item-create"><img src="~/img/action/add24.png" alt="@(Localiser["label.create"])" title="@(Localiser["label.create"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-5 sax-label text-left text-sm-right">@Localiser["user.caption.user"]</div>
<div class="col-sm-7">@Html.TextBoxFor(model => model.Filter.User, new { @id = "ifFilterUser", @class = "FilterUser", @maxlength = "32", @onkeypress = "sax.refreshResults(250)" })</div>
</div>
}
</div>
</div>
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item User">
<div class="sax-header">
<div class="floatl sax-caption"><div class="UserEmailAddress"></div></div>
<div class="floatr sax-action sax-item-update"><img src="~/img/action/edit24.png" alt="@(Localiser["label.update"])" title="@(Localiser["label.update"])" /></div>
<div class="clearb"></div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-4"><span class="UserDisplayName"></span></div>
<div class="col-sm-4"><div>Roles: <span class="UserRoleCount"></span></div></div>
<div class="col-sm-4 right"><div class="StatusDesc"></div></div>
</div>
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-4"><div class=""></div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<partial name="Panel-UserDialog.cshtml" />
@@ -0,0 +1,83 @@
@model Model_User
@inject SaxLocaliser Localiser
@{
/* Users - User dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createUser = '@Localiser.GetJavascriptText("user.caption.user.create")';
sax.dlg.title.updateUser = '@Localiser.GetJavascriptText("user.caption.user.update")';
sax.dlg.title.selectUserRoles = '@Localiser.GetJavascriptText("user.caption.select.user.roles")';
sax.data.userRoleCodeDelim = '@Model.UserRoleCodeDelim';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.UserAccountIdValue, new { @class = "UserAccountId" })
}
<div id="dlgUser" class="sax-dialog" title="">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUser", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.UserAccountIdValue, new { @class = "UserAccountId" })
@* User email *@
<div class="sax-form-control UserEmailAddress">
<div class="sax-label">
<label for="ifUserEmailAddress"><span><span class="required">*</span> @Localiser["label.email.address"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.UserEmailAddress, new { @id = "ifUserEmailAddress", @class = "UserEmailAddress sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sax-message"></div>
</div>
<div class="sax-form-control UserTitle">
<div class="sax-label">
<label for="cbUserTitle"><span><span class="required"></span> @Localiser["label.title"]</span></label>
</div>
<div class="sax-control">
@Html.DropDownListFor(model => model.UserTitle, Model.TitlesForSelect, Localiser["label.select"], new { @id = "cbUserTitle", @class = "UserTitle sax-select" })
</div>
<div class="sax-message"></div>
</div>
@* User first name *@
<div class="sax-form-control UserFirstName">
<div class="sax-label">
<label for="ifUserFirstName"><span><span class="required">*</span> @Localiser["label.first.name"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.UserFirstName, new { @id = "ifUserFirstName", @class = "UserFirstName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_FirstName) })
</div>
<div class="sax-message"></div>
</div>
@* User last name *@
<div class="sax-form-control UserLastName">
<div class="sax-label">
<label for="ifUserLastName"><span><span class="required">*</span> @Localiser["label.last.name"]</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.UserLastName, new { @id = "ifUserLastName", @class = "UserLastName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn.ColumnIdx.UserAccount_LastName) })
</div>
<div class="sax-message"></div>
</div>
@* User roles *@
<div class="sax-form-control UserRoles">
<div class="sax-label">
<label for="lbUserRoles"><span>@Localiser["label.user.roles"]</span></label>
</div>
<div class="sax-control row">
@Html.HiddenFor(model => model.UserRoleCodes, new { @id = "ifUserRoleCodes", @class = "UserRoleCodes sax-input" })
<div class="col"><textarea id="ifUserRoles" class="UserRoles sax-input" style="" rows="3" readonly="readonly" onDblClick="sax.selectUserRoles()"></textarea></div>
<div class="fixed"><button id="" class="sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px" onclick="sax.selectUserRoles()">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
}
<partial name="~/Views/Common/CodeSelection.cshtml" />
</div>
@@ -0,0 +1,7 @@
@using Sarand.Net.Standard.Base.Core
@using Sarand.Net.Standard.Base.Utils
@using Saxonite.Base.App
@using Saxonite.Net.Standard.Base.App
@using Saxonite.Net.Core.Web.Base.App
@using Saxonite.Net.Core.Web.Admin.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -0,0 +1,4 @@
@inject Saxonite.Net.Core.Web.Base.App.SaxLocaliser Localiser
@{
Layout = "_Layout";
}
@@ -0,0 +1,26 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"EmailConfigs": {
"SAX-SF-Dev": {
"Message": {
"AddressFrom": "DoNotReply@solumforti.com"
},
"SMTP": {
"Server": "smtp.ionos.co.uk",
"ServerPort": 587,
"UseSSL": true
},
"Send": {
"Using": 2,
"UserName": "info@solumforti.com",
"Password": "solfor-info-5Ekur3"
}
}
}
}
@@ -0,0 +1,57 @@
{
"SessionConfigs": {
"Saxonite-Dev": {
"Environments": [ "Development" ],
"Instance": "Dev",
"CoreDatabase": {
"Location": "MsSql:Sarand106\\SQL2014.Saxonite_Dev",
"User": "saxonite",
"Password": "sax-dev-2021"
},
"Email": {
"Context": "SAX-SF-Dev",
"ContactUs": {
"AddressesTo": [ "dev@solumforti.com" ]
}
},
"Documents": {
"RootFolder": "D:\\Saxonite\\Data\\Docs"
},
"Logging": {
"RootFolder": "D:\\Sarand\\Projects\\User\\AHoward\\VS2017\\Saxonite\\Logs"
}
},
"Saxonite-Test": {
"Environments": [ "Production" ],
"Instance": "QC",
"CoreDatabase": {
"Location": "MsSql:Sarand106\\SQL2014.Saxonite_Dev",
"User": "saxonite",
"Password": "sax-dev-2021"
},
"Email": {
"Context": "SAX-SF-Dev",
"ContactUs": {
"AddressesTo": [ "qc@solumforti.com" ],
"AddressesBcc": [ ]
}
},
"Documents": {
"RootFolder": "D:\\Saxonite\\Data\\Docs"
},
"Logging": {
"RootFolder": "D:\\Sarand\\Projects\\User\\AHoward\\VS2017\\Saxonite\\QC\\Logs"
},
"Variants": [
{
"ApplicationRootFolder": "D:\\Sarand\\Projects\\User\\AHoward\\VS2017\\Saxonite\\QC2\\Bin",
"Instance": "QC",
"Logging": {
"RootFolder": "D:\\Sarand\\Projects\\User\\AHoward\\VS2017\\Saxonite\\QC2\\Logs"
}
}
]
}
}
}
@@ -0,0 +1,57 @@
{
"SessionConfigs": {
"Saxonite-Dev": {
"Environments": [ "Development" ],
"Instance": "Dev",
"CoreDatabase": {
"Location": "MsSql:Sarand107\\SQL2016.Saxonite_Dev",
"User": "saxonite",
"Password": "sax-dev-2021"
},
"Email": {
"Context": "SAX-SF-Dev",
"ContactUs": {
"AddressesTo": [ "dev@solumforti.com" ]
}
},
"Documents": {
"RootFolder": "D:\\Sarand\\Projects\\Users\\AHoward\\VS2019\\Saxonite\\Docs"
},
"Logging": {
"RootFolder": "D:\\Sarand\\Projects\\Users\\AHoward\\VS2019\\Saxonite\\Logs\\Admin"
}
},
"Saxonite-Test": {
"Environments": [ "Production" ],
"Instance": "QC",
"CoreDatabase": {
"Location": "MsSql:Sarand106\\SQL2014.Saxonite_Dev",
"User": "saxonite",
"Password": "sax-dev-2021"
},
"Email": {
"Context": "SAX-SF-Dev",
"ContactUs": {
"AddressesTo": [ "qc@solumforti.com" ],
"AddressesBcc": [ ]
}
},
"Documents": {
"RootFolder": "D:\\Sarand\\Projects\\Users\\AHoward\\VS2019\\Saxonite\\Docs"
},
"Logging": {
"RootFolder": "D:\\Sarand\\Projects\\Users\\AHoward\\VS2019\\Saxonite\\QC\\Logs\\Admin"
},
"Variants": [
{
"ApplicationRootFolder": "D:\\Sarand\\Projects\\Users\\AHoward\\VS2019\\Saxonite\\QC2\\App",
"Instance": "QC",
"Logging": {
"RootFolder": "D:\\Sarand\\Projects\\Users\\AHoward\\VS2019\\Saxonite\\QC2\\Logs\\Admin"
}
}
]
}
}
}
@@ -0,0 +1,60 @@
{
"SessionConfigs": {
"SAX-Prod": {
"Environments": [ "Production" ],
"Instance": "",
"CoreDatabase": {
"Location": "MsSql:.\\SQLExpress2012.Saxonite_Core_Demo",
"User": "sax-demo-user",
"Password": "sax-demo-202105"
},
"Security": {
"Password": {
"MinLength": 9
},
"SignIn": {
"KeepLoggedIn": {
"Allowed": true,
"CookieExpiresDays": 5
},
"PasswordRequest": {
"LinkExpiresMins": 30
}
}
},
"Email": {
"Context": "SAX-SF-Prod",
"ContactUs": {
"AddressesTo": [],
"AddressesBcc": [ "dev@solumforti.com" ]
},
"Signup": {
"Activate": {
"AddressesBcc": [ "signup@solumforti.com" ]
},
"Welcome": {
"AddressesBcc": [ "signup@solumforti.com" ]
}
}
},
"Documents": {
"RootFolder": "C:\\Sarand\\Saxonite\\Demo\\Admin\\Docs"
},
"Logging": {
"RootFolder": "C:\\Sarand\\Saxonite\\Demo\\Admin\\Logs"
},
"Variants": [
{
"ApplicationRootFolder": "C:\\Sarand\\Saxonite\\Demo\\Admin\\App",
"Instance": "Demo",
"Documents": {
"RootFolder": "C:\\Sarand\\Saxonite\\Demo\\Admin\\Docs"
},
"Logging": {
"RootFolder": "C:\\Sarand\\Saxonite\\Demo\\Admin\\Logs"
}
}
]
}
}
}
@@ -0,0 +1,60 @@
{
"SessionConfigs": {
"SAX-Prod": {
"Environments": [ "Production" ],
"Instance": "",
"CoreDatabase": {
"Location": "MsSql:.\\SQL2016EXP.Saxonite_Core_Live",
"User": "sax-prod-user",
"Password": "sax-live-202104"
},
"Security": {
"Password": {
"MinLength": 8
},
"SignIn": {
"KeepLoggedIn": {
"Allowed": true,
"CookieExpiresDays": 30
},
"PasswordRequest": {
"LinkExpiresMins": 30
}
}
},
"Email": {
"Context": "SAX-SF-Prod",
"ContactUs": {
"AddressesTo": [],
"AddressesBcc": [ "dev@solumforti.com" ]
},
"Signup": {
"Activate": {
"AddressesBcc": [ "signup@solumforti.com" ]
},
"Welcome": {
"AddressesBcc": [ "signup@solumforti.com" ]
}
}
},
"Documents": {
"RootFolder": "C:\\Sarand\\Saxonite\\Prod\\SaxoniteAdmin\\Docs"
},
"Logging": {
"RootFolder": "C:\\Sarand\\Saxonite\\Prod\\SaxoniteAdmin\\Logs"
},
"Variants": [
{
"ApplicationRootFolder": "C:\\Sarand\\Saxonite\\PreProd\\SaxoniteAdmin\\Bin",
"Instance": "PreProd",
"Documents": {
"RootFolder": "C:\\Sarand\\Saxonite\\PreProd\\SaxoniteAdmin\\Docs"
},
"Logging": {
"RootFolder": "C:\\Sarand\\Saxonite\\PreProd\\SaxoniteAdmin\\Logs"
}
}
]
}
}
}
@@ -0,0 +1,2 @@
{
}
@@ -0,0 +1,19 @@
{
"EmailConfigs": {
"SAX-SF-Prod": {
"Message": {
"AddressFrom": "DoNotReply@solumforti.com"
},
"SMTP": {
"Server": "smtp.ionos.co.uk",
"ServerPort": 587,
"UseSSL": true
},
"Send": {
"Using": 2,
"UserName": "info@solumforti.com",
"Password": "solfor-info-5Ekur3"
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
+10
View File
@@ -0,0 +1,10 @@
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jqueryui@1.12.1",
"destination": "wwwroot/lib/jquery-ui/"
}
]
}
@@ -0,0 +1,415 @@
/*
Admin - Base stylesheet.
*/
body
{
margin: 0;
padding: 0;
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 1rem;
line-height: 1.25rem;
background-color: #fff;
overflow: auto;
}
div, button, input, select, textarea, table, tr, td
{
font-family: inherit;
font-size: 1rem;
}
:focus, button:focus
{
outline: none;
box-shadow: 0 0 0 2px #80808040 inset;
}
/* Fonts */
/* Common styles */
h1, h2, h3
{
color: #404040;
}
h1
{
font-size: 14px;
font-weight: bold;
margin: 6px 0 6px 0;
padding: 0;
}
h2
{
font-size: 12px;
font-weight: bold;
margin: 3px 0 6px 0;
padding: 0;
}
h3
{
font-size: 11px;
font-weight: bold;
margin: 3px 0 6px 0;
padding: 0;
}
div, form
{
margin: 0;
padding: 0;
}
p
{
margin: 3px 0 6px 0;
}
hr
{
height: 4px;
width: 90%;
text-align: center;
color: #000;
margin: 4px auto;
}
a
{
font-size: 1rem;
}
img
{
border-style: none;
}
ul
{
margin: 3px 0 6px 0;
}
table
{
margin: 0;
border-collapse: separate;
border-spacing: 2px;
border-width: 0;
empty-cells: inherit;
}
th, td
{
vertical-align: top;
padding: 0;
}
label
{
margin: 0;
}
input
{
margin: 0;
padding: 0 2px 1px 2px;
}
textarea
{
resize: none;
}
.bold, b
{
font-weight: bold !important;
}
.ul, u
{
text-decoration: underline !important;
}
.italic, i
{
font-style: italic !important;
}
.upper
{
text-transform: uppercase !important;
}
.lower
{
text-transform: lowercase !important;
}
.center
{
text-align: center !important;
}
.right
{
text-align: right !important;
}
.middle
{
vertical-align: middle !important;
}
.bottom
{
vertical-align: bottom !important;
}
.pointer
{
cursor: pointer;
}
.action
{
cursor: pointer;
}
.action:hover
{
text-decoration: underline
}
td.action
{
text-align: center;
}
.fullwidth
{
width: 100%;
}
.inlineblock, .inlineBlock, .iblock, .inlineb, .ib
{
display: inline-block;
}
.floatl, .floatleft
{
float: left;
}
.floatr, .floatright
{
float: right;
}
.clearl, .clearleft
{
clear: left;
}
.clearr, .clearright
{
clear: right;
}
.clearb, .clearboth
{
clear: both;
margin: 0;
padding: 0;
width: 0;
height: 0;
line-height: 0.0em;
font-size: 0;
}
.clear-after:after
{
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
.nowrap
{
white-space: nowrap !important;
}
.spacer
{
height: 4px;
font-size: 0;
line-height: 0;
}
.spacer2
{
height: 8px;
font-size: 0;
line-height: 0;
}
.spacer4
{
height: 16px;
font-size: 0;
line-height: 0;
}
.spacer8
{
height: 32px;
font-size: 0;
line-height: 0;
}
.pt2
{
padding-top: 2px !important;
}
.pt4
{
padding-top: 4px !important;
}
.pt8
{
padding-top: 8px !important;
}
.pt12
{
padding-top: 12px !important;
}
.pt16
{
padding-top: 16px !important;
}
.pb2
{
padding-bottom: 2px !important;
}
.pb4
{
padding-bottom: 4px !important;
}
.pb8
{
padding-bottom: 8px !important;
}
.pb12
{
padding-bottom: 12px !important;
}
.pb16
{
padding-bottom: 16px !important;
}
.pl2
{
padding-left: 2px !important;
}
.pl4
{
padding-left: 4px !important;
}
.pl8
{
padding-left: 8px !important;
}
.pl12
{
padding-left: 12px !important;
}
.pl16
{
padding-left: 16px !important;
}
.pr2
{
padding-right: 2px !important;
}
.pr4
{
padding-right: 4px !important;
}
.pr8
{
padding-right: 8px !important;
}
.pr12
{
padding-right: 12px !important;
}
.pr16
{
padding-right: 16px !important;
}
.lastTabSeq
{
z-index: 99;
}
.hidden
{
display: none;
}
.shadow
{
-moz-box-shadow: 2px 2px 3px #ccc;
-webkit-box-shadow: 2px 2px 3px #ccc;
box-shadow: 2px 2px 3px #ccc; /* For IE 8 */
-ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc')"; /* For IE 5.5 - 7 */
filter: progid:DXImageTransform.Microsoft.Shadow(Strength=3,Direction=135,Color='#cccccc');
}
/* Bootstrap */
.container, .container-fluid
{
padding: 0;
}
.row
{
margin: 0;
}
.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto,
.col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto,
.col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto,
.col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto,
.col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto
{
padding: 0;
}
/* The End */
@@ -0,0 +1,36 @@
/*
Admin - Code Selection stylesheet.
*/
/* Panels */
.sax-codeSelection-codes
{
padding:2px 0;
background-color:#f0f0f0;
border: 1px solid #b0b0b0;
border-radius: 6px;
}
#dlgCodeSelection.sax-form button.sax-button
{
margin: 4px 0;
}
.sax-panel-item.Code
{
margin: 2px 4px;
padding: 2px 4px;
background-color:#fff;
border: 2px solid #d8d8ff;
border-radius: 4px;
cursor:pointer;
}
.sax-panel-item.Code.sel
{
background-color: #ececff;
border-color: #8a8aff;
}
/* The End */
@@ -0,0 +1,139 @@
/*
Admin - Data stylesheet.
*/
/* Filter panel */
.sax-panel-filter
{
margin: 8px auto;
border: solid 2px #979797;
border-radius: 6px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
}
@media (min-width: 576px)
{
.sax-panel-filter
{
}
}
.sax-panel-filter .sax-header
{
padding: 2px 8px;
min-height: 24px;
color: #f0f0f0;
background-color: #979797;
border-bottom: solid 2px #979797;
}
.sax-panel-filter .sax-header .sax-caption
{
margin-top: 4px;
font-size: 1.2rem;
font-weight: bold;
}
.sax-panel-filter .sax-header .sax-action
{
margin: 2px 0 0 8px;
border-radius: 4px;
}
.sax-panel-filter .sax-header .sax-action:hover
{
background-color: #b0b0b0;
box-shadow: 0 0 4px 2px #80808080;
}
.sax-panel-filter .sax-content
{
margin: 4px 8px;
padding: 4px 8px;
}
.sax-panel-filter .sax-content .sax-label
{
padding-top: 2px;
padding-right: 12px;
text-align: right;
}
.sax-panel-filter .sax-content .sax-button
{
min-width: auto;
margin-left: 4px;
padding: 4px 6px;
}
/* Data Items panel */
.sax-panel-results
{
}
/* Messages */
.sax-panel-results .sax-panel-messages .sax-message
{
margin-top: 8px;
padding-left: 24px;
font-size: 16px;
background-position: left top;
background-repeat: no-repeat;
}
.sax-panel-results .sax-panel-messages .sax-message.sax-error
{
color: #e3120b;
background-image: url("../img/icon/error-18.png");
}
/* Item */
.sax-panel-item-template
{
display: none;
}
.sax-panel-item
{
margin: 8px auto;
border: solid 2px #979797;
border-radius: 6px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
overflow:hidden;
}
.sax-panel-item .sax-header
{
padding: 2px 8px;
min-height: 24px;
background-color: #e0e0e0;
border-bottom: solid 2px #979797;
}
.sax-panel-item .sax-caption
{
margin-top: 4px;
font-size: 1.2rem;
font-weight: bold;
}
.sax-panel-item .sax-content
{
margin: 4px 8px;
padding: 4px 4px;
}
@media (min-width: 576px)
{
.sax-panel-filter
{
}
}
/* The End */
@@ -0,0 +1,186 @@
/*
Admin - Form stylesheet.
*/
/* Panels */
.sax-panel-info, .sax-panel-form
{
max-width: 720px;
margin: 24px auto;
padding: 24px 24px;
border: solid 2px #979797;
border-color: var(--color-primary);
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
}
.sax-panel-form.security
{
max-width:520px;
}
@media (min-width: 576px)
{
.sax-panel-info, .sax-panel-form
{
}
}
/* Form */
.sax-form
{
}
.sax-form .sax-heading
{
margin-bottom: 20px;
}
.sax-form .sax-heading .sax-caption
{
font-size: 1.5rem;
font-weight: bold;
color: var(--color-primary);
}
/* Controls */
.sax-form .sax-form-control
{
margin: 0 0 16px 0;
}
.sax-form .sax-form-control .sax-label
{
margin: 0 0 8px 0;
font-size: 1.00rem;
}
.sax-form .sax-form-control .sax-label-text
{
font-size: 1.00rem;
}
.sax-form .sax-form-control .sax-note
{
font-size: 0.9rem;
}
.sax-form .sax-input, .sax-form .sax-textarea, .sax-form .sax-select
{
padding: 4px 8px;
width: 100%;
font-size: 1.00rem;
background-color: #fff;
border: 1px solid #595959;
border-radius: 2px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
outline: none;
}
.sax-form .sax-textarea, .sax-form .sax-select[multiple="multiple"]
{
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
overflow-y: scroll;
}
.sax-form .sax-input:read-only, .sax-form .sax-textarea:read-only
{
background-color: #f8f8f8;
}
.sax-form .sax-form-control.sax-error .sax-input, .sax-form .sax-form-control.sax-error .sax-textarea, .sax-form .sax-form-control.sax-error .sax-select
{
color: #DC143C;
border-color: #DC143C;
}
/* Control messages */
.sax-form .sax-form-control .sax-message
{
display: none;
margin-top: 8px;
padding-left: 24px;
font-size: 1.00rem;
background-position: left top;
background-repeat: no-repeat;
}
.sax-form .sax-form-control.sax-error .sax-message
{
display: block;
color: #e3120b;
background-image: url("../img/icon/error-18.png");
}
/* Buttons */
.sax-form button.sax-button,
.sax-form input[type="button"].sax-button,
.sax-form input[type="submit"].sax-button
{
height: auto;
width: auto;
min-width: 80px;
padding: 4px 2px;
font-size: 1rem;
text-align: center;
color: #fff;
background-color: #d4af37;
border: 1px solid #d4af37;
border-radius: 4px;
}
.sax-form button.sax-button:hover,
.sax-form input[type="button"].sax-button:hover,
.sax-form input[type="submit"].sax-button:hover
{
}
/* Password strength */
#prgPasswordStrength
{
width: 200px;
height: 18px;
margin: 6px 24px;
border-color: #595959;
-moz-box-shadow: none;
-webkit-box-shadow: none;
overflow: hidden;
}
#prgPasswordStrength .ui-widget-header
{
margin: 0;
height: 20px;
background: none;
background-color: #D2222D;
border: solid 0 #D2222D;
border-radius: 0;
}
#prgPasswordStrength.amber .ui-widget-header
{
background-color: #FFBF00;
border-color: #FFBF00;
}
#prgPasswordStrength.green .ui-widget-header
{
background-color: #238823;
border-color: #238823;
}
#prgPasswordStrength.ui-corner-all
{
border-radius: 8px;
}
/* The End */
@@ -0,0 +1,358 @@
/*
Admin - Main stylesheet.
*/
/* Global variables */
:root
{
/* #4b0082 - Indigo. Ref. https://www.crispedge.com/color/4b0082 */
--color-primary: #4b0082;
--color-light: #9b71b9;
--color-pale: #ebe2f1;
/* Tetrad/Square colors */
--color-alt1: #820a00;
--color-alt2: #378200;
--color-alt3: #007882;
}
/* Document */
html
{
font-size: 14px;
}
@media (min-width: 768px)
{
html
{
font-size: 16px;
}
}
body
{
font-family: Arial, Verdana, Helvetica, Lucida, sans-serif;
}
body > .container
{
padding: 0 1%;
}
/* Sticky footer styles */
html
{
position: relative;
min-height: 100%;
}
body
{
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer
{
position: absolute;
bottom: 0;
width: 100%;
padding:0 2%;
white-space: nowrap;
/* Set the fixed height of the footer here */
height: 60px;
line-height: 60px; /* Vertically center the text there */
}
/* Header */
a.navbar-brand
{
white-space: normal;
text-align: center;
word-break: break-all;
}
.border-top
{
border-top: 1px solid #e5e5e5;
}
.border-bottom
{
border-bottom: 1px solid #e5e5e5;
}
.box-shadow
{
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy
{
font-size: 1rem;
line-height: inherit;
}
/* Page caption */
.sax-page-caption
{
margin: 0;
padding: 16px 24px;
font-size: 48px;
font-weight: bold;
color: #000;
}
/* Actions */
a, a:hover
{
text-decoration: underline;
color: #d4af37;
}
a:focus
{
outline-color: #b0b0b0;
outline-width: thin;
}
.sax-action, .sax-action-text
{
cursor: pointer;
}
.sax-action-text:hover
{
text-decoration: underline;
}
.sax-action-link
{
display: inline-block;
font-size: 1rem;
font-weight: bold;
color: #000;
cursor: pointer;
}
.sax-action-link:hover
{
opacity: .7;
transition: all .4s ease-in-out;
}
.sax-link-text
{
font-size: 18px;
font-weight: bold;
}
/* Buttons */
.sax-button-cta, .sax-button-action
{
display: inline-block;
padding: 12px 12px;
min-width: 100px;
color: #404040;
background-color: #e0e0e0;
font-size: 16px;
font-weight: bold;
text-align: center;
border-radius: 20px;
cursor: pointer;
transition: all .5s ease-in-out;
outline: none;
}
.sax-button-action
{
}
.sax-button-action:hover
{
background-color: #b0b0b0;
}
.sax-button-action:focus
{
outline: none;
box-shadow: 0 0 4px 2px #808080;
}
.sax-button-cta:hover
{
background: #808080 !important;
}
/* --- Notification --- */
#sax-notification
{
display: none;
position: fixed;
top: 0;
right: 0;
min-width: 180px;
margin-right: 12px;
z-index: 1000;
border: 1px solid #808080;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
overflow: hidden;
}
#sax-notification .content
{
background-color: #fbfbfb;
overflow: hidden;
}
#sax-notification .heading
{
font-size: 16px;
font-weight: bold;
background-color: #f0f0f0;
border-bottom: 1px solid #808080;
}
#sax-notification .heading:after
{
content: "";
display: table;
clear: both;
}
#sax-notification .title
{
float: left;
padding: 4px 8px;
white-space: nowrap;
}
#sax-notification .close
{
float: right;
padding: 4px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
#sax-notification .message
{
clear: both;
padding: 8px;
font-size: 14px;
}
/* --- Modal Please wait --- */
#sax-please-wait
{
display: none;
cursor: progress;
position: fixed;
z-index: 1001;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(250,250,250,.25) url('../img/animated/InProgress-01.gif') 50% 50% no-repeat;
}
/* Dialogs */
.sax-dialog
{
display: none;
}
.ui-dialog
{
position: fixed;
padding: 0;
}
.ui-dialog .ui-dialog-titlebar
{
padding: 6px 12px;
}
.ui-dialog .ui-dialog-content
{
padding: 6px 12px;
}
.ui-dialog.ui-widget.ui-widget-content
{
border-width: 2px;
border-color: #d4af37;
-moz-box-shadow: 4px 4px 6px #ccc;
-webkit-box-shadow: 4px 4px 6px #ccc;
overflow: hidden;
}
.ui-dialog.ui-widget.ui-widget-content.ui-corner-all
{
border-radius: 6px;
}
.ui-dialog.ui-widget .ui-widget-header
{
font-size: 1.1rem;
color: #fff;
background-color: #d4af37;
border-color: #d4af37;
}
.ui-widget-content
{
font-size: 16px;
color: #000;
}
.ui-dialog .ui-dialog-titlebar.ui-corner-all
{
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.ui-dialog.no-close .ui-dialog-titlebar-close
{
/*display: none;*/
}
.ui-dialog .ui-dialog-buttonpane
{
border-color: #d4af37;
}
.ui-dialog .ui-dialog-buttonpane button
{
color: #fff;
background-color: #d4af37;
border-color: #d4af37;
outline: none;
}
.ui-widget-overlay
{
background-color: #b0b0b0;
opacity: 0.25;
}
/* Date Picker */
.ui-datepicker
{
}
.ui-datepicker .ui-datepicker-header
{
background-color: #fff;
border: 1px solid #d4af37;
}
/* The End */
@@ -0,0 +1,47 @@
/*
Admin - Code Selection stylesheet.
*/
/* Panels */
#dlgOrganisationSelection .sax-panel-filter
{
border-width: 1px;
}
#dlgOrganisationSelection .sax-panel-filter .sax-content
{
margin:4px 8px;
padding:2px 4px;
}
#dlgOrganisationSelection .sax-selection-results.Organisations
{
padding:2px 0;
background-color:#f0f0f0;
border: 1px solid #b0b0b0;
border-radius: 6px;
}
#dlgOrganisationSelection.sax-form button.sax-button
{
margin: 4px 0;
}
.sax-panel-item.Organisation
{
margin: 2px 4px;
padding: 2px 4px;
background-color:#fff;
border: 2px solid #d8d8ff;
border-radius: 4px;
cursor:pointer;
}
.sax-panel-item.Organisation.sel
{
background-color: #ececff;
border-color: #8a8aff;
}
/* The End */
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Some files were not shown because too many files have changed in this diff Show More