Portal signin via Control Instance

This commit is contained in:
Andy Howard
2024-10-29 08:40:47 +00:00
parent bf6bb966bc
commit c5ee285e27
467 changed files with 25771 additions and 3347 deletions
@@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class Program
@@ -2,9 +2,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Consoles;
using Sarand.BusinessMgr.Web.Control.Consoles;
using Sarand.BusinessMgr.Net.Core.Web.Helpers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Net.Standard.Dbas.Utils;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
@@ -17,15 +20,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmApplication_Control : SbmApplication_Web
{
//-------------------------------------------------------------------
// Data members.
private bool generateDatabaseDefinitions_Control = true;
private bool generateDatabaseDefinitions_BusMgr = true;
private bool generateDatabaseDefinitions_Control = false;
private bool generateDatabaseDefinitions_BusMgr = false;
private PropertySet propertySet;
private Dictionary<String, SbmAppConfig> appConfigs = new Dictionary<string, SbmAppConfig>();
private Dictionary<String, SbmEmailConfig> emailConfigs = new Dictionary<string, SbmEmailConfig>();
@@ -42,8 +45,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
SessionContext = sessionContext;
}
public SbmSessionContext_Web SessionContext { get; }
public List<string> DbIdentifiers { get; } = new List<string>();
public ISacsDatabase BusMgrDatabase { get; set; }
public HashSet<string> DbIdentifiers { get; } = new HashSet<string>();
public ISacsDatabase BusMgrDatabase { get; set; }
}
private readonly Dictionary<ushort, SessionContextData> sessionContexts = new Dictionary<ushort, SessionContextData>();
@@ -87,16 +90,23 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
{
return templateDatabase;
}
public override List<string> GetDbIdentifiers(SbmSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.DbIdentifiers ?? new List<string>();
}
public override ISacsDatabase GetBusMgrDatabase(SbmSessionContext sessionContext)
public override HashSet<string> GetDbIdentifiers(SbmSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.DbIdentifiers ?? new HashSet<string>();
}
public override ISacsDatabase GetBusMgrDatabase(SbmSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.BusMgrDatabase;
}
public override SbmEmailConfig EmailConfig { get { return emailConfigs.Values.ToList().getConfigForSession(AppConfig); } }
//-------------------------------------------------------------------
// Create Consoles.
public override SbmEmailConsole CreateEmailConsole(ISbmUserContext_Core userContext)
{
return new SbmEmailConsole_Control(EmailConfig);
}
//-------------------------------------------------------------------
// Initialisation.
/// <summary>Initialise environment - from Startup.</summary>
@@ -182,8 +192,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
if (sessionContextIdx_Control < 1)
sessionContextIdx_Control = sessionContextIdx;
SessionContextData sessionContextData = new SessionContextData(new SbmSessionContext_Web(sessionContextIdx, Sbm.Application.WebControl));
sessionContextData.DbIdentifiers.AddRange(dbConfig.Identifiers);
sessionContextData.BusMgrDatabase = busMgrDatabase;
sessionContextData.DbIdentifiers.UnionWith(dbConfig.Identifiers);
sessionContextData.BusMgrDatabase = busMgrDatabase;
sessionContexts.Add(sessionContextData.SessionContext.Index, sessionContextData);
}
}
@@ -202,7 +212,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
config.location = AppConfig.TemplateDatabase.Location;
config.user = AppConfig.TemplateDatabase.User;
config.pword = AppConfig.TemplateDatabase.Password;
GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance.BusinessMgr, GetTemplateDatabase());
GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance.Core, GetTemplateDatabase());
}
}
@@ -210,7 +220,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
// Generate database definition.
private void GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance dbInstance, ISacsDatabase db)
{
if (!generateDatabaseDefinitions_BusMgr || !SbmSystem.DebugMode) return;
if (!SbmSystem.DebugMode) return;
if (!StringUtil.EmptyOnNull(EnvironmentName).EqualsCI("Development")) return;
if (db == null || StringUtil.IsEmpty(db.ConnectionString)) return;
@@ -219,7 +229,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
}
private void GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance dbInstance, String connectionString)
{
if (!generateDatabaseDefinitions_BusMgr || !SbmSystem.DebugMode) return;
if (!SbmSystem.DebugMode) return;
if (!StringUtil.EmptyOnNull(EnvironmentName).EqualsCI("Development")) return;
if (StringUtil.IsEmpty(connectionString)) return;
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web
namespace Sarand.BusinessMgr.Web
{
// <summary>
/// Dummy classes to group shared resources - named as per '.resx' file in root namespace.
@@ -19,7 +19,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web
// Ref. Project Properties - Application.
}
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmLocaliser_Control : SbmLocaliser
@@ -7,35 +7,110 @@ using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Database;
using System.Collections.Generic;
using System;
using Newtonsoft.Json;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.BusinessMgr.Web.Control.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using DocumentFormat.OpenXml.Wordprocessing;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmSessionData_Control : SbmSessionData_Web
{
//-------------------------------------------------------------------
// Construction.
[JsonConstructor]
private SbmSessionData_Control(long sessionIdx, int itemIdx, SbmSessionContext sessionContext) :
base(sessionIdx, itemIdx, sessionContext)
{ }
/// <summary>
/// SessionData construction.
/// <para>SessionContext and UserAccount must be supplied.</para>
/// <para>Organisation is optional.</para>
/// </summary>
public SbmSessionData_Control(SbmSessionContext sessionContext) :
base(sessionContext)
{
}
//=======================================================================
public class SbmSessionData_Control : SbmSessionData_Web
{
//-------------------------------------------------------------------
// Construction.
[JsonConstructor]
private SbmSessionData_Control(long sessionIdx, int itemIdx, SbmSessionContext sessionContext) :
base(sessionIdx, itemIdx, sessionContext)
{ }
/// <summary>
/// SessionData construction.
/// <para>SessionContext and UserAccount must be supplied.</para>
/// </summary>
public SbmSessionData_Control(SbmSessionContext sessionContext, SbmcUserAccount userAccount) :
base(sessionContext)
{
User = new UserData_Control(sessionContext, userAccount);
}
//-------------------------------------------------------------------
protected override UserData CreateUserData(SbmSessionContext sessionContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, List<SysIdString> supervisedOrganisations)
{
return null;
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
}
//=======================================================================
[JsonIgnore]
public override String UserDisplayName { get { return User?.DisplayName; } }
[JsonIgnore]
public override String UserEmailAddress { get { return User?.EmailAddress; } }
[JsonIgnore]
public override bool ValidUser { get { return SysId.IsValid(User?.UserAccountId); } }
[JsonIgnore]
public override bool KeepLoggedIn { get { return User.KeepLoggedIn; } }
[JsonIgnore]
public override DbUpdateInfo DbUpdateInfo
{
get
{
return new DbUpdateInfo(User?.UserAccountId, StringUtil.OnEmpty(User.DisplayName, User.EmailAddress), SbmSystem.ComputerName);
}
}
//-------------------------------------------------------------------
// User details.
public class UserData_Control : UserData
{
[JsonConstructor]
private UserData_Control()
{ }
internal UserData_Control(SbmSessionContext sessionContext, SbmcUserAccount userAccount) :
base(sessionContext)
{
UserAccountId = userAccount.Id;
if (SysId.IsValid(UserAccountId))
{
UserAccountSupplied = true;
DisplayName = userAccount.GetDisplayName();
Title = userAccount.Title;
FirstName = userAccount.FirstName;
LastName = userAccount.LastName;
EmailAddress = userAccount.EmailAddress;
}
}
internal UserData_Control(UserData userData) :
base(userData)
{
UserData_Control userData_Control = userData as UserData_Control;
if (userData_Control != null)
{
UserAccountId = userData_Control.UserAccountId;
UserAccountSupplied = userData_Control.UserAccountSupplied;
KeepLoggedIn= userData_Control.KeepLoggedIn;
}
DisplayName = userData.DisplayName;
Title = userData.Title;
FirstName = userData.FirstName;
LastName = userData.LastName;
EmailAddress = userData.EmailAddress;
}
// Properties.
public SysId UserAccountId { get; private set; }
public bool UserAccountSupplied { get; private set; }
public bool KeepLoggedIn { get; private set; }
}
protected UserData CreateUserData(SbmSessionContext sessionContext, SbmcUserAccount userAccount)
{
return null;
}
/// <summary>User - JSON deserialisation route.</summary>
public UserData_Control User { get; private set; }
//-------------------------------------------------------------------
// Control application encryption IV value - for consistent encryption during session.
public String Base64IV_ControlApp { get; private set; } = SacsEncryptionHelper.InitSymmetricEncryptionIV(SbmController_Control.Base64Key_ControlApp);
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmStartup_Control : SbmStartup_Web
@@ -0,0 +1,87 @@
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Dbas;
using System;
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
/// <summary>
/// Wrapper class to allow access to core internals from Control application.
/// </summary>
public class SbmUserContext_Control : ISbmUserContext_Core
{
//-------------------------------------------------------------------
public SbmUserContext_Control(SbmSessionData_Control sessionData)
{
SessionData = sessionData;
}
//-------------------------------------------------------------------
// Properties.
private SbmSessionData_Control SessionData { get; }
//-------------------------------------------------------------------
/// <summary>Session Context - Identifies database connections etc.</summary>
public SbmSessionContext SessionContext { get { return null; } }
//-------------------------------------------------------------------
// User properties.
/// <summary>User Security Item Id.</summary>
public SysId UserSecurityItemId { get { return null; } }
/// <summary>User Affiliate Id.</summary>
public SysId UserAffiliateId { get { return null; } }
/// <summary>User display name.</summary>
public String UserDisplayName { get { return SessionData.UserDisplayName; } }
/// <summary>User email address.</summary>
public String UserEmailAddress { get { return SessionData.UserEmailAddress; } }
/// <summary>Does the context represent a valid user account.</summary>
public bool ValidUser { get { return SessionData.ValidUser; } }
/// <summary>Database update info - user session specific properties for database inserts/updates.</summary>
public DbUpdateInfo DbUpdateInfo { get { return SessionData.DbUpdateInfo; } }
//-------------------------------------------------------------------
/// <summary>Security profile - what the user is allowed to do based on security roles and groups.</summary>
public ISbmSecurityProfile_Core SecurityProfile_Core { get; }
//-------------------------------------------------------------------
// Organisation properties.
/// <summary>Organisation Affiliate Id.</summary>
public SysId OrganisationAffiliateId { get; }
/// <summary>Organisation Affiliate Type.</summary>
public Sbm.AffiliateType OrganisationAffiliateType { get; }
/// <summary>Organisation 'Internal' Association Id.</summary>
public SysId OrganisationAssociationId { get; }
/// <summary>Organisation display name.</summary>
public String OrganisationDisplayName { get; }
/// <summary>Organisation Interface context - to qualify interfaces belonging to the organisation.</summary>
public String OrganisationInterfaceContext { get; }
//-------------------------------------------------------------------
// Organisation configuration.
public String GetOrganisationOption(Sbm.OrganisationOption option, String context) { return ""; }
public String GetOrganisationOption(String optionName, String context) { return ""; }
public String GetOwnerOption_Value(Sbm.OrgOption_Value eCol) { return ""; }
public String GetOwnerOption_Folder(Sbm.OrgOption_Folder eCol) { return ""; }
public bool GetOwnerOption_AllowOption(Sbm.OrgOption_AllowOption eCol) { return false; }
public String GetOwnerOption_Caption(Sbm.OrgOption_Caption eCol, String defaultValue) { return ""; }
public bool GetOwnerOption_ShowField(SbmDbDfn.ColumnIdx eCol) { return false; }
public bool GetOwnerOption_ShowColumn(Sbm.OrgOption_ShowColumn eCol) { return false; }
public String GetOwnerOption_FieldLabel(SbmDbDfn.ColumnIdx eCol, String defautValue) { return ""; }
public int GetOwnerOption_Minutes(Sbm.OrgOption_Minutes eLimit, int defautValue) { return 0; }
public int GetOwnerOption_UpdateLimit(Sbm.OrgOption_PeriodDays eLimit, int defautValue) { return 0; }
public int GetOwnerOption_UpdateLimit(Sbm.OrgOption_PeriodWeeks eLimit, int defautValue) { return 0; }
public int GetOwnerOption_NumericDecimals(Sbm.OrgOption_NumericDecimals eNumDecimals, int defautValue = 2) { return 2; }
public Sbm.CommodityType GetOwnerOption_CommodityType(Sbm.OrgOption_CommodityType commodityTypeContext) { return Sbm.CommodityType.Unknown; }
public SysId GetOwnerOption_StockLocation(Sbm.OrgOption_StockLocation eLoc) { return null; }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,38 @@
using Sarand.BusinessMgr.Net.Core.Web.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Email;
namespace Sarand.BusinessMgr.Web.Control.Consoles
{
//=======================================================================
public class SbmEmailConsole_Control : SbmEmailConsole_Web
{
//-------------------------------------------------------------------
// Construction.
public SbmEmailConsole_Control(SbmEmailConfig emailConfig) :
base(emailConfig)
{
}
//-------------------------------------------------------------------
/// <summary>
/// Replace dynamic tags in email message body.
/// <para></para>
/// </summary>
public void ReplaceDynamicEmailBodyTags(SaEmailData emailData, SbmcUserAccount userAccount)
{
// Replace core tags, eg. main stylesheet.
SbmEmailConsole_Web.ReplaceDynamicEmailTags(emailData);
if (userAccount != null)
{
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,422 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Microsoft.AspNetCore.Mvc;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Web.Control.Models;
using Sarand.BusinessMgr.Web.Control.Models.Account;
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 System;
using System.Collections.Generic;
using System.Linq;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Account
{
//=======================================================================
partial class UserAccountController
{
//-------------------------------------------------------------------
// Definitions.
// User data.
private class UserListData : AjaxResponse_Data.ItemData_Base
{
public String EmailAddress { get; set; }
public String DisplayName { get; set; }
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String Phone { get; set; }
public String LastLoginOn { get; set; }
public String LastLoginAt { get; set; }
}
private class UserItemData : UserListData
{
public String Initials { get; set; }
public String PhoneMain { get; set; }
public String PhoneMobile { get; set; }
public String PhoneAlt { get; set; }
}
private void PopulateUserResponseListData(SbmModel_Control model, UserListData data, SbmcUserAccount user)
{
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.Phone = StringUtil.OnBlank(user.PhoneMain, user.PhoneMobile, user.PhoneAlt);
data.LastLoginOn = model.FormatDateForDisplay(user.LastLoginAt);
data.LastLoginAt = model.FormatDateTimeForDisplay(user.LastLoginAt);
data.StatusCode = user.StatusCode;
data.StatusDesc = user.StatusDesc;
}
private void PopulateUserResponseItemData(SbmModel_Control model, UserItemData data, SbmcUserAccount user)
{
PopulateUserResponseListData(model, data, user);
data.Initials = user.Initials;
data.PhoneMain = user.PhoneMain;
data.PhoneMobile = user.PhoneMobile;
data.PhoneAlt = user.PhoneAlt;
}
//-------------------------------------------------------------------
// Get User List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_UserList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserList(SbmModel_Control model) : base(model) { }
// User list.
public List<UserListData> Users { get; } = new List<UserListData>();
// Reference data.
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
public String StatusCodeDefault { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/UserAccount/Ajax/GetList")]
[Route("Account/UserAccount/Ajax/GetUsers")]
[Route("Account/UserAccount/Ajax/GetUserList")]
public IActionResult Ajax_GetUserList(Model_UserAccount model)
{
String fn = GetType().Name + ".Ajax_GetUserList";
String messageTitle = Localiser["label.users"];
AjaxResponse_UserList responseData = new AjaxResponse_UserList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - statuses.
foreach (SaStatus status in SbmcUserAccount.ValidStatuses)
{
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
if (status != SaStatus.Deleted)
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
}
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcUserAccount.DefaultStatus);
// Item filter.
SbmcUserAccount.Params prmUserAccount = new SbmcUserAccount.Params();
prmUserAccount.Search = model.Filter.Search;
prmUserAccount.StatusCat = model.Filter.GetStatusCat();
model.Paging.ItemTotal = (ulong)ControlAccountMgr.GetUserAccountCount(prmUserAccount);
model.PopulatePagingTo(prmUserAccount.ResultsPaging);
// Item columns.
ColumnDfn colId = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.Id);
ColumnDfn colEmail = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.EmailAddress);
ColumnDfn colTitle = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.Title);
ColumnDfn colFirstName = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.FirstName);
ColumnDfn colLastName = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.LastName);
ColumnDfn colPhoneMain = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.PhoneMain);
ColumnDfn colPhoneMobile = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.PhoneMobile);
ColumnDfn colPhoneAlt = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.PhoneAlt);
ColumnDfn colLastLogin = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.LastLoginAt);
ColumnDfn colStatus = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colEmail, colTitle, colFirstName, colLastName,
colPhoneMain, colPhoneMobile, colPhoneAlt, colLastLogin, colStatus
};
// Query items.
using (DbResultSet resultSet = ControlAccountMgr.ExecuteUserAccountQuery(columns, prmUserAccount))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
UserListData userData = new UserListData()
{
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),
Phone = StringUtil.OnBlank(values.GetString(colPhoneMain.DataName), values.GetString(colPhoneMobile.DataName), values.GetString(colPhoneAlt.DataName)),
LastLoginOn = model.FormatDateForDisplay(values.GetDateTime(colLastLogin.DataName)),
LastLoginAt = model.FormatDateTimeForDisplay(values.GetDateTime(colLastLogin.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
userData.DisplayName = SbmcUserAccount.GetDisplayName(userData.FirstName, userData.LastName);
responseData.Users.Add(userData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Users.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_User : AjaxResponse_Data
{
// Construction.
public AjaxResponse_User(SbmModel_Control model) : base(model) { }
// User details.
public UserItemData user { get; } = new UserItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/UserAccount/Ajax/GetItem")]
[Route("Account/UserAccount/Ajax/GetUser")]
public IActionResult Ajax_GetUser(Model_UserAccount model)
{
String fn = GetType().Name + ".Ajax_GetUser";
String messageTitle = Localiser["label.user"];
AjaxResponse_User responseData = new AjaxResponse_User(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Identify item.
SysId userAccountId = model.User.Id.ToSysId();
if (!SysId.IsValid(userAccountId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SbmcUserAccount user = ControlAccountMgr.GetUserAccountById(userAccountId);
disposables.Add(user);
if (user == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Id: '" + userAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateUserResponseItemData(model, responseData.user, user);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User by email ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/UserAccount/Ajax/GetUserByEmail")]
public IActionResult Ajax_GetUserByEmail(Model_UserAccount model)
{
String fn = GetType().Name + ".Ajax_GetUserByEmail";
String messageTitle = Localiser["label.user"];
AjaxResponse_User responseData = new AjaxResponse_User(model);
StoreSessionData = false;
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Identify user.
if (StringUtil.IsEmpty(model.Filter.EmailAddress))
{
SaLogger.Warn($"{SysUtil.Fn} Attempt to get user details with blank email.");
return Json(responseData);
}
// Retrieve user.
SbmcUserAccount user = ControlAccountMgr.GetUserAccountByEmailAddress(model.Filter.EmailAddress);
disposables.Add(user);
if (user == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Email: " + model.Filter.EmailAddress);
return Json(responseData);
}
// Construct response.
PopulateUserResponseItemData(model, responseData.user, user);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store User ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/UserAccount/Ajax/StoreItem")]
[Route("Account/UserAccount/Ajax/StoreUser")]
public IActionResult Ajax_StoreUser(Model_UserAccount model)
{
String fn = GetType().Name + ".Ajax_StoreUser";
String messageTitle = Localiser["label.user"];
AjaxResponse_User responseData = new AjaxResponse_User(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Validate item Id.
SysId userAccountId = model.User.Id.ToSysId();
if (StringUtil.ToBool(model.User.Exists) && !SysId.IsValid(userAccountId))
{
SaLogger.Warn($"{SysUtil.Fn} User Id not supplied for existing item.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!StringUtil.ToBool(model.User.Exists) && SysId.IsValid(userAccountId))
{
SaLogger.Warn($"{SysUtil.Fn} User Id supplied for new item.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Validate item properties.
model.ValidateUserData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve existing item.
SbmcUserAccount user = ControlAccountMgr.GetUserAccountById(userAccountId);
disposables.Add(user);
if (user == null && SysId.IsValid(userAccountId))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Id: '" + model.User.Id + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Create new item if required.
if (user == null)
{
user = ControlAccountMgr.CreateUserAccount();
if (model.User.EmailAddress != null)
user.EmailAddress = model.User.EmailAddress;
}
// Populate item properties.
if (model.User.Title != null)
user.Title = model.User.Title;
if (model.User.FirstName != null)
user.FirstName = model.User.FirstName;
if (model.User.Initials != null)
user.Initials = model.User.Initials;
if (model.User.LastName != null)
user.LastName = model.User.LastName;
if (model.User.PhoneMain != null)
user.PhoneMain = model.User.PhoneMain;
if (model.User.PhoneMobile != null)
user.PhoneMobile = model.User.PhoneMobile;
if (model.User.PhoneAlt != null)
user.PhoneAlt = model.User.PhoneAlt;
if (model.User.StatusCode != null)
user.Status = model.User.GetStatus();
// Store item.
if (!SysId.IsValid(user.Id))
{
if (!ControlAccountMgr.InsertUserAccount(user, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to insert user: " + ControlAccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlAccountMgr.ErrorStateMessage));
}
}
else
{
if (!ControlAccountMgr.UpdateUserAccount(user, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to update user [Id:" + SysId.ToString(user.Id) + "]: " + ControlAccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlAccountMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateUserResponseItemData(model, responseData.user, user);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Send User Emails ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/UserAccount/Ajax/SendEmails")]
public IActionResult Ajax_SendEmails(Model_UserAccount model)
{
String fn = GetType().Name + ".Ajax_SendEmails";
String messageTitle = Localiser["label.emails"];
AjaxResponse_User responseData = new AjaxResponse_User(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve user.
SysId userAccountId = model.User.Id.ToSysId();
SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(userAccountId);
disposables.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Id: '" + model.User.Id + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Email types to send.
bool sendAcountActivationPortal = StringUtil.ToBool(model.UserEmailAcountActivationPortal);
bool sendRequestPasswordPortal = StringUtil.ToBool(model.UserEmailRequestPasswordPortal);
bool sendRequestPasswordControl = StringUtil.ToBool(model.UserEmailRequestPasswordControl);
if (!sendAcountActivationPortal && !sendRequestPasswordPortal && !sendRequestPasswordControl)
return Json(responseData.SetMessage(messageTitle, Localiser["msg.validation.select.option"]));
// Send emails.
int emailsSentCount = 0;
if (sendAcountActivationPortal)
{
//if (!OrganisationController.SendAccountActivatedEmail(this, userAccount))
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Account Activation email."));
//emailsSentCount++;
}
if (sendRequestPasswordPortal)
{
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, true))
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Request Password email."));
emailsSentCount++;
}
if (sendRequestPasswordControl)
{
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, false))
return Json(responseData.SetMessage(messageTitle, "Failed to send Control Request Password email."));
emailsSentCount++;
}
// Construct response.
PopulateUserResponseItemData(model, responseData.user, userAccount);
responseData.SetNotification(messageTitle, "Emails sent: " + emailsSentCount);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Web.Control.Models.Account;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Account
{
//======================================================================
public partial class UserAccountController : SbmController_Control
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public UserAccountController(SbmLocaliser localiser, ILogger<UserAccountController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[Route("Account/UserAccount")]
public IActionResult Account_UserAccount()
{
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
AutoLoginUser();
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
return RedirectToAction("", "Security");
}
// UserAccount page.
Model_UserAccount model = new Model_UserAccount(Localiser);
return View("Views/Account/UserAccount/Index.cshtml", model);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -1,14 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Control.Models;
using Sarand.BusinessMgr.Web.Control.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
namespace Sarand.BusinessMgr.Web.Control.Controllers
{
//======================================================================
public class HomeController : SbmController_Control
@@ -26,8 +26,18 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
//-------------------------------------------------------------------
public IActionResult Index()
{
// Home page.
Model_Home model = new Model_Home(Localiser);
if (!ValidUserSession)
AutoLoginUser();
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
return RedirectToAction("", "Security");
}
// Home page.
Model_Home model = new Model_Home(Localiser);
return View(model);
}
@@ -44,6 +54,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
//-------------------------------------------------------------------
}
//======================================================================
//======================================================================
}
@@ -0,0 +1,290 @@
using Microsoft.AspNetCore.Mvc;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Web.Control.Models;
using Sarand.BusinessMgr.Web.Control.Models.Instance;
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 System;
using System.Collections.Generic;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
{
//=======================================================================
partial class OwnerController
{
//-------------------------------------------------------------------
// Definitions.
// Owner data.
private class OwnerListData : AjaxResponse_Data.ItemData_Base
{
public String Name { get; set; }
public String ContactName { get; set; }
public String ContactEmailAddress { get; set; }
public String ContactPhone { get; set; }
public long? RepositoryCount { get; set; }
}
private class OwnerItemData : OwnerListData
{
public String ContactPhoneMain { get; set; }
public String ContactPhoneAlt { get; set; }
}
private void PopulateOwnerResponseListData(SbmModel_Control model, OwnerListData data, SbmcOwner owner)
{
data.Id = SysId.ToString(owner.Id);
data.Name = owner.Name;
data.ContactName = owner.ContactName;
data.ContactEmailAddress = owner.ContactEmailAddress;
data.ContactPhone = StringUtil.OnBlank(owner.ContactPhoneMain, owner.ContactPhoneAlt);
data.StatusCode = owner.StatusCode;
data.StatusDesc = owner.StatusDesc;
}
private void PopulateOwnerResponseItemData(SbmModel_Control model, OwnerItemData data, SbmcOwner owner)
{
PopulateOwnerResponseListData(model, data, owner);
data.ContactPhoneMain = owner.ContactPhoneMain;
data.ContactPhoneAlt = owner.ContactPhoneAlt;
}
//-------------------------------------------------------------------
// Get Owner List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_OwnerList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_OwnerList(SbmModel_Control model) : base(model) { }
// Owner list.
public List<OwnerListData> Owners { get; } = new List<OwnerListData>();
// Reference data.
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
public String StatusCodeDefault { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Instance/Owner/Ajax/GetList")]
[Route("Instance/Owner/Ajax/GetOwners")]
[Route("Instance/Owner/Ajax/GetOwnerList")]
public IActionResult Ajax_GetOwnerList(Model_Owner model)
{
String fn = GetType().Name + ".Ajax_GetOwnerList";
String messageTitle = Localiser["label.owners"];
AjaxResponse_OwnerList responseData = new AjaxResponse_OwnerList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - statuses.
foreach (SaStatus status in SbmcOwner.ValidStatuses)
{
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
if (status != SaStatus.Deleted)
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
}
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcOwner.DefaultStatus);
// Item filter.
SbmcOwner.Params prmOwner = new SbmcOwner.Params();
prmOwner.Search = model.Filter.Search;
prmOwner.StatusCat = model.Filter.GetStatusCat();
model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetOwnerCount(prmOwner);
model.PopulatePagingTo(prmOwner.ResultsPaging);
// Item columns.
ColumnDfn colId = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Id);
ColumnDfn colName = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Name);
ColumnDfn colContactName = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactName);
ColumnDfn colContactEmail = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactEmailAddress);
ColumnDfn colContactPhoneMain = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactPhoneMain);
ColumnDfn colContactPhoneAlt = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactPhoneAlt);
ColumnDfn colRepoCount = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.RepositoryCount);
ColumnDfn colStatus = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colName, colContactName, colContactEmail,
colContactPhoneMain, colContactPhoneAlt, colRepoCount, colStatus
};
// Query items.
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteOwnerQuery(columns, prmOwner))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
OwnerListData ownerData = new OwnerListData()
{
Id = values.GetString(colId.DataName),
Name = values.GetString(colName.DataName),
ContactName = values.GetString(colContactName.DataName),
ContactEmailAddress = values.GetString(colContactEmail.DataName),
ContactPhone = StringUtil.OnBlank(values.GetString(colContactPhoneMain.DataName), values.GetString(colContactPhoneAlt.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
long repositoryCount = values.GetLong(colRepoCount.DataName);
if (repositoryCount > 0)
ownerData.RepositoryCount = repositoryCount;
responseData.Owners.Add(ownerData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Owners.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Owner ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Owner : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Owner(SbmModel_Control model) : base(model) { }
// Owner details.
public OwnerItemData owner { get; } = new OwnerItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Instance/Owner/Ajax/GetItem")]
[Route("Instance/Owner/Ajax/GetOwner")]
public IActionResult Ajax_GetOwner(Model_Owner model)
{
String fn = GetType().Name + ".Ajax_GetOwner";
String messageTitle = Localiser["label.owner"];
AjaxResponse_Owner responseData = new AjaxResponse_Owner(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Identify item.
SysId ownerId = model.Owner.Id.ToSysId();
if (!SysId.IsValid(ownerId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
disposables.Add(owner);
if (owner == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + ownerId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateOwnerResponseItemData(model, responseData.owner, owner);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Owner ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Instance/Owner/Ajax/StoreItem")]
[Route("Instance/Owner/Ajax/StoreOwner")]
public IActionResult Ajax_StoreOwner(Model_Owner model)
{
String fn = GetType().Name + ".Ajax_StoreOwner";
String messageTitle = Localiser["label.owner"];
AjaxResponse_Owner responseData = new AjaxResponse_Owner(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Validate item Id.
SysId ownerId = model.Owner.Id.ToSysId();
if (StringUtil.ToBool(model.Owner.Exists) && !SysId.IsValid(ownerId))
{
SaLogger.Warn($"{SysUtil.Fn} Owner Id not supplied for existing item.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!StringUtil.ToBool(model.Owner.Exists) && SysId.IsValid(ownerId))
{
SaLogger.Warn($"{SysUtil.Fn} Owner Id supplied for new item.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Validate item properties.
model.ValidateOwnerData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve existing item.
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
disposables.Add(owner);
if (owner == null && SysId.IsValid(ownerId))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + model.Owner.Id + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Create new item if required.
if (owner == null)
{
owner = ControlInstanceMgr.CreateOwner();
}
// Populate item properties.
if (model.Owner.Name != null)
owner.Name = model.Owner.Name;
if (model.Owner.ContactName != null)
owner.ContactName = model.Owner.ContactName;
if (model.Owner.ContactEmailAddress != null)
owner.ContactEmailAddress = model.Owner.ContactEmailAddress;
if (model.Owner.ContactPhoneMain != null)
owner.ContactPhoneMain = model.Owner.ContactPhoneMain;
if (model.Owner.ContactPhoneAlt != null)
owner.ContactPhoneAlt = model.Owner.ContactPhoneAlt;
if (model.Owner.StatusCode != null)
owner.Status = model.Owner.GetStatus();
// Store item.
if (!SysId.IsValid(owner.Id))
{
if (!ControlInstanceMgr.InsertOwner(owner, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to insert owner: " + ControlInstanceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
}
}
else
{
if (!ControlInstanceMgr.UpdateOwner(owner, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to update owner [Id:" + SysId.ToString(owner.Id) + "]: " + ControlInstanceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateOwnerResponseItemData(model, responseData.owner, owner);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Web.Control.Models.Instance;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
{
//======================================================================
public partial class OwnerController : SbmController_Control
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public OwnerController(SbmLocaliser localiser, ILogger<OwnerController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[Route("Instance/Owner")]
public IActionResult Index()
{
if (!ValidUserSession)
AutoLoginUser();
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
return RedirectToAction("", "Security");
}
// Owner page.
Model_Owner model = new Model_Owner(Localiser);
return View("Views/Instance/Owner/Index.cshtml", model);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -0,0 +1,313 @@
using Microsoft.AspNetCore.Mvc;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Web.Control.Models;
using Sarand.BusinessMgr.Web.Control.Models.Instance;
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 System;
using System.Collections.Generic;
using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Security;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
{
//=======================================================================
partial class RepositoryController
{
//-------------------------------------------------------------------
// Definitions.
private static String PasswordDummyValue = "********";
// Repository data.
private class RepositoryListData : AjaxResponse_Data.ItemData_Base
{
public String OwnerName { get; set; }
public String Context { get; set; }
public String ServerName { get; set; }
public String InstanceName { get; set; }
public String DatabaseName { get; set; }
}
private class RepositoryItemData : RepositoryListData
{
public String OwnerId { get; set; }
public String UserName { get; set; }
public String PasswordDummy { get; } = PasswordDummyValue;
}
private void PopulateRepositoryResponseListData(SbmModel_Control model, RepositoryListData data, SbmcOwner owner, SbmcRepository repository)
{
data.Id = SysId.ToString(repository.Id);
data.OwnerName = owner.Name;
data.Context = repository.Context;
data.ServerName = repository.ServerName;
data.InstanceName = repository.InstanceName;
data.DatabaseName = repository.DatabaseName;
data.StatusCode = repository.StatusCode;
data.StatusDesc = repository.StatusDesc;
}
private void PopulateRepositoryResponseItemData(SbmModel_Control model, RepositoryItemData data, SbmcOwner owner, SbmcRepository repository)
{
PopulateRepositoryResponseListData(model, data, owner, repository);
data.OwnerId = SysId.ToString(repository.OwnerId);
data.UserName = repository.UserName;
}
//-------------------------------------------------------------------
// Get Repository List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_RepositoryList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RepositoryList(SbmModel_Control model) : base(model) { }
// Repository list.
public List<RepositoryListData> Repositories { get; } = new List<RepositoryListData>();
// Reference data.
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
public String StatusCodeDefault { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Instance/Repository/Ajax/GetList")]
[Route("Instance/Repository/Ajax/GetRepositories")]
[Route("Instance/Repository/Ajax/GetRepositoryList")]
public IActionResult Ajax_GetRepositoryList(Model_Repository model)
{
String fn = GetType().Name + ".Ajax_GetRepositoryList";
String messageTitle = Localiser["label.repositories"];
AjaxResponse_RepositoryList responseData = new AjaxResponse_RepositoryList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - statuses.
foreach (SaStatus status in SbmcRepository.ValidStatuses)
{
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
if (status != SaStatus.Deleted)
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
}
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcRepository.DefaultStatus);
// Item filter.
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
prmRepository.Search = model.Filter.Search;
prmRepository.StatusCat = model.Filter.GetStatusCat();
model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetRepositoryCount(prmRepository);
model.PopulatePagingTo(prmRepository.ResultsPaging);
// Item columns.
ColumnDfn colId = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Id);
ColumnDfn colOwnerName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.OwnerName);
ColumnDfn colContext = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Context);
ColumnDfn colServerName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.ServerName);
ColumnDfn colInstanceName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.InstanceName);
ColumnDfn colDatabaseName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.DatabaseName);
ColumnDfn colStatus = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOwnerName, colContext, colServerName, colInstanceName,
colDatabaseName, colStatus
};
// Query items.
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteRepositoryQuery(columns, prmRepository))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
RepositoryListData repositoryData = new RepositoryListData()
{
Id = values.GetString(colId.DataName),
OwnerName = values.GetString(colOwnerName.DataName),
Context = values.GetString(colContext.DataName),
ServerName = values.GetString(colServerName.DataName),
InstanceName = values.GetString(colInstanceName.DataName),
DatabaseName = values.GetString(colDatabaseName.DataName),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Repositories.Add(repositoryData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Repositories.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Repository ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Repository : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Repository(SbmModel_Control model) : base(model) { }
// Repository details.
public RepositoryItemData repository { get; } = new RepositoryItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Instance/Repository/Ajax/GetItem")]
[Route("Instance/Repository/Ajax/GetRepository")]
public IActionResult Ajax_GetRepository(Model_Repository model)
{
String fn = GetType().Name + ".Ajax_GetRepository";
String messageTitle = Localiser["label.repository"];
AjaxResponse_Repository responseData = new AjaxResponse_Repository(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Identify item.
SysId repositoryId = model.Repository.Id.ToSysId();
if (!SysId.IsValid(repositoryId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SbmcRepository repository = ControlInstanceMgr.GetRepositoryById(repositoryId);
disposables.Add(repository);
if (repository == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve repository for Id: '" + repositoryId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Owner.
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(repository.OwnerId);
disposables.Add(owner);
if (owner == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + repository.OwnerId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Construct response.
PopulateRepositoryResponseItemData(model, responseData.repository, owner, repository);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Repository ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Instance/Repository/Ajax/StoreItem")]
[Route("Instance/Repository/Ajax/StoreRepository")]
public IActionResult Ajax_StoreRepository(Model_Repository model)
{
String fn = GetType().Name + ".Ajax_StoreRepository";
String messageTitle = Localiser["label.repository"];
AjaxResponse_Repository responseData = new AjaxResponse_Repository(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Validate item Id.
SysId repositoryId = model.Repository.Id.ToSysId();
if (StringUtil.ToBool(model.Repository.Exists) && !SysId.IsValid(repositoryId))
{
SaLogger.Warn($"{SysUtil.Fn} Repository Id not supplied for existing item.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!StringUtil.ToBool(model.Repository.Exists) && SysId.IsValid(repositoryId))
{
SaLogger.Warn($"{SysUtil.Fn} Repository Id supplied for new item.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Validate item properties.
model.ValidateRepositoryData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve existing item.
SbmcRepository repository = ControlInstanceMgr.GetRepositoryById(repositoryId);
disposables.Add(repository);
if (repository == null && SysId.IsValid(repositoryId))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve repository for Id: '" + model.Repository.Id + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Owner.
SysId ownerId = repository?.OwnerId ?? StringUtil.ToSysId(model.Repository.OwnerId);
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
disposables.Add(owner);
if (owner == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + ownerId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Create new item if required.
if (repository == null)
{
repository = ControlInstanceMgr.CreateRepository();
}
// Populate item properties.
repository.OwnerId = owner.Id;
if (model.Repository.Context != null)
repository.Context = model.Repository.Context;
if (model.Repository.ServerName != null)
repository.ServerName = model.Repository.ServerName;
if (model.Repository.InstanceName != null)
repository.InstanceName = model.Repository.InstanceName;
if (model.Repository.DatabaseName != null)
repository.DatabaseName = model.Repository.DatabaseName;
if (model.Repository.UserName != null)
repository.UserName = model.Repository.UserName;
if (model.Repository.Password != null && !model.Repository.Password.Equals(PasswordDummyValue))
repository.Password = model.Repository.Password;
if (model.Repository.StatusCode != null)
repository.Status = model.Repository.GetStatus();
// Store item.
if (!SysId.IsValid(repository.Id))
{
if (!ControlInstanceMgr.InsertRepository(repository, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to insert repository: " + ControlInstanceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
}
}
else
{
if (!ControlInstanceMgr.UpdateRepository(repository, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to update repository [Id:" + SysId.ToString(repository.Id) + "]: " + ControlInstanceMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateRepositoryResponseItemData(model, responseData.repository, owner, repository);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Web.Control.Models.Instance;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
{
//======================================================================
public partial class RepositoryController : SbmController_Control
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public RepositoryController(SbmLocaliser localiser, ILogger<RepositoryController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[Route("Instance/Repository")]
public IActionResult Index()
{
if (!ValidUserSession)
AutoLoginUser();
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
return RedirectToAction("", "Security");
}
// Repository page.
Model_Repository model = new Model_Repository(Localiser);
return View("Views/Instance/Repository/Index.cshtml", model);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -0,0 +1,426 @@
using DocumentFormat.OpenXml.InkML;
using DocumentFormat.OpenXml.Wordprocessing;
using Microsoft.AspNetCore.Mvc;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Web.Control.Models;
using Sarand.BusinessMgr.Web.Control.Models.Param;
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 System;
using System.Collections.Generic;
using static Sarand.BusinessMgr.Net.Standard.Base.App.SbmSysParamCache;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Param
{
//=======================================================================
partial class ParamController
{
//-------------------------------------------------------------------
// Definitions.
// Param data.
private class ParamListData : AjaxResponse_Data.ItemData_Base
{
public String ItemCaption { get; set; }
public String ParamType { get; set; }
public String ParamCaption { get; set; }
public String Name { get; set; }
public String Context { get; set; }
public String Value { get; set; }
public bool? Password { get; set; }
}
private class ParamItemData : ParamListData
{
public String ItemType { get; set; }
public String ItemId { get; set; }
}
private void PopulateParamResponseListData(SbmModel_Control model, ParamListData data, SbmcItemParam itemParam)
{
data.Id = SysId.ToString(itemParam.Id);
data.ItemCaption = itemParam.ItemId.Type.ToString() + ": " + itemParam.ItemId.Id;
data.Name = itemParam.Name;
data.Context = itemParam.Context;
if (itemParam.IsPasswordValue())
data.Password = true;
else
data.Value = itemParam.Value;
data.StatusCode = itemParam.StatusCode;
data.StatusDesc = itemParam.StatusDesc;
}
private void PopulateParamResponseItemData(SbmModel_Control model, ParamItemData data, SbmcItemParam itemParam)
{
PopulateParamResponseListData(model, data, itemParam);
data.ItemType = itemParam.ItemId.Type.ToString();
data.ItemId = SysId.ToString(itemParam.ItemId.Id);
data.ItemCaption = ControlItemMgr.GetItemCaption(itemParam.ItemId);
data.Notes = itemParam.Notes;
}
//-------------------------------------------------------------------
// Get Filter Items ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_FilterItemList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_FilterItemList(SbmModel_Control model) : base(model) { }
// Items.
public List<ItemIdName> Items { get; } = new List<ItemIdName>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Param/Param/Ajax/GetFilterItems")]
public IActionResult Ajax_GetParamFilterItemList(Model_Param model)
{
String messageTitle = Localiser["label.params"];
AjaxResponse_FilterItemList responseData = new AjaxResponse_FilterItemList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
SbmcHierarchyItem.ItemType itemType = EnumUtil.ParseEnum(model.Filter.ItemType, SbmcHierarchyItem.ItemType.Unknown);
if (itemType == SbmcHierarchyItem.ItemType.Unknown)
{
SaLogger.Warn($"{SysUtil.Fn} Item type not specified.");
return Json(responseData);
}
if (itemType == SbmcHierarchyItem.ItemType.Owner)
{
// Item filter.
SbmcOwner.Params prmOwner = new SbmcOwner.Params();
prmOwner.StatusCat = SbmcOwner.StatusCat.ActiveOnly;
//model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetOwnerCount(prmOwner);
//model.PopulatePagingTo(prmOwner.ResultsPaging);
// Item columns.
ColumnDfn colId = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Id);
ColumnDfn colName = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Name);
List<ColumnDfn> columns = new List<ColumnDfn>() { colId, colName };
// Query items.
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteOwnerQuery(columns, prmOwner))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
AjaxResponse_Data.ItemIdName itemParamData = new AjaxResponse_Data.ItemIdName()
{
Id = values.GetString(colId.DataName),
Name = values.GetString(colName.DataName),
};
responseData.Items.Add(itemParamData);
}
}
//if (model.Paging.IsPaged)
// model.Paging.ItemCount = (ushort)responseData.Items.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
}
else if (itemType == SbmcHierarchyItem.ItemType.Repository)
{
// Item filter.
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
prmRepository.StatusCat = SbmcRepository.StatusCat.ActiveOnly;
//model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetRepositoryCount(prmRepository);
//model.PopulatePagingTo(prmRepository.ResultsPaging);
// Item columns.
ColumnDfn colId = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Id);
ColumnDfn colName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Context);
List<ColumnDfn> columns = new List<ColumnDfn>() { colId, colName };
// Query items.
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteRepositoryQuery(columns, prmRepository))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
AjaxResponse_Data.ItemIdName itemParamData = new AjaxResponse_Data.ItemIdName()
{
Id = values.GetString(colId.DataName),
Name = values.GetString(colName.DataName),
};
responseData.Items.Add(itemParamData);
}
}
//if (model.Paging.IsPaged)
// model.Paging.ItemCount = (ushort)responseData.Items.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Param List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_ParamList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ParamList(SbmModel_Control model) : base(model) { }
// Items.
public List<ParamListData> Params { get; } = new List<ParamListData>();
// Reference data.
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
public String StatusCodeDefault { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Param/Param/Ajax/GetList")]
[Route("Param/Param/Ajax/GetParams")]
[Route("Param/Param/Ajax/GetParamList")]
public IActionResult Ajax_GetParamList(Model_Param model)
{
String messageTitle = Localiser["label.params"];
AjaxResponse_ParamList responseData = new AjaxResponse_ParamList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - statuses.
foreach (SaStatus status in SbmcItemParam.ValidStatuses)
{
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
if (status != SaStatus.Deleted)
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
}
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcItemParam.DefaultStatus);
// Item filter.
SbmcItemParam.Params prmParam = new SbmcItemParam.Params();
ItemId itemId = new ItemId(StringUtil.ToShort(model.Filter.ItemType), StringUtil.ToSysId(model.Filter.ItemId));
if (ItemId.IsValid(itemId))
prmParam.ItemIdsIncluded.Add(itemId);
prmParam.Search = model.Filter.Search;
prmParam.Search = model.Filter.Search;
prmParam.StatusCat = model.Filter.GetStatusCat();
model.Paging.ItemTotal = (ulong)ControlItemMgr.GetItemParamCount(prmParam);
model.PopulatePagingTo(prmParam.ResultsPaging);
// Item columns.
ColumnDfn colId = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Id);
ColumnDfn colItemCaption = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Item_Caption);
ColumnDfn colContext = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Context);
ColumnDfn colName = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Name);
ColumnDfn colValue = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Value);
ColumnDfn colFlags = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Status);
ColumnDfn colStatus = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colItemCaption, colContext, colName, colValue, colFlags, colStatus
};
// Query items.
using (DbResultSet resultSet = ControlItemMgr.ExecuteItemParamQuery(columns, prmParam))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
String paramContext = values.GetString(colContext.DataName);
String paramName = values.GetString(colName.DataName);
ushort paramFlags = (ushort)values.GetLong(colFlags.DataName);
ParamListData itemParamData = new ParamListData()
{
Id = values.GetString(colId.DataName),
ItemCaption = ControlItemMgr.GetItemParamDataValue(colItemCaption, values).ToString(),
Context = values.GetString(colContext.DataName),
Name = values.GetString(colName.DataName),
Value = ControlItemMgr.GetItemParamDataValue(colValue, values).ToString(),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
if (SbmcItemParam.IsPasswordValue(paramContext, paramName, paramFlags))
itemParamData.Password = true;
responseData.Params.Add(itemParamData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Params.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Param ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Param : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Param(SbmModel_Control model) : base(model) { }
// Param details.
public ParamItemData Param { get; } = new ParamItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Param/Param/Ajax/GetItem")]
[Route("Param/Param/Ajax/GetParam")]
public IActionResult Ajax_GetParam(Model_Param model)
{
String messageTitle = Localiser["label.param"];
AjaxResponse_Param responseData = new AjaxResponse_Param(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Identify item.
SysId itemParamId = model.Param.Id.ToSysId();
if (!SysId.IsValid(itemParamId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SbmcItemParam itemParam = ControlItemMgr.GetItemParamById(itemParamId);
disposables.Add(itemParam);
if (itemParam == null)
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve itemParam for Id: '" + itemParamId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateParamResponseItemData(model, responseData.Param, itemParam);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Param ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Param/Param/Ajax/StoreItem")]
[Route("Param/Param/Ajax/StoreParam")]
public IActionResult Ajax_StoreParam(Model_Param model)
{
String messageTitle = Localiser["label.param"];
AjaxResponse_Param responseData = new AjaxResponse_Param(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Validate item Id.
SysId itemParamId = model.Param.Id.ToSysId();
if (StringUtil.ToBool(model.Param.Exists) && !SysId.IsValid(itemParamId))
{
SaLogger.Warn($"{SysUtil.Fn} Id not supplied for existing Item Param.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!StringUtil.ToBool(model.Param.Exists) && SysId.IsValid(itemParamId))
{
SaLogger.Warn($"{SysUtil.Fn} Id supplied for new Item Param.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Validate item properties.
model.ValidateParamData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve existing item.
SbmcItemParam itemParam = ControlItemMgr.GetItemParamById(itemParamId);
disposables.Add(itemParam);
if (itemParam == null && SysId.IsValid(itemParamId))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve Item Param for Id: '" + model.Param.Id + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Contruct item id.
ItemId itemId = new ItemId(StringUtil.ToShort(model.Param.ItemType), model.Param.ItemId.ToSysId());
if (itemParam != null && !itemParam.ItemId.Equals(itemId))
{
SaLogger.Warn($"{SysUtil.Fn} Inconsistent Item Id specified for Item Param. Expected - Type: {(SbmcHierarchyItem.ItemType)itemParam.ItemId.Type}, Id: {itemParam.ItemId.Id}. Submitted - Type: {(SbmcHierarchyItem.ItemType)itemId.Type}, Id: {itemId.Id} ");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Create new item if required.
if (itemParam == null)
{
if (!ItemId.IsValid(itemId))
{
SaLogger.Warn($"{SysUtil.Fn} Item Id not specified for new Item Param.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!SbmcItemParam.ValidItemTypes.Contains((SbmcHierarchyItem.ItemType)itemId.Type))
{
SaLogger.Warn($"{SysUtil.Fn} Invalid item type ({(SbmcHierarchyItem.ItemType)itemId.Type}) specified for new Item Param.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
if (StringUtil.IsBlank(ControlItemMgr.GetItemCaption(itemId)))
{
SaLogger.Warn($"{SysUtil.Fn} Invalid item (type: {(SbmcHierarchyItem.ItemType)itemId.Type}, id: {itemId.Id}) specified for new Item Param.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Create parameter.
itemParam = ControlItemMgr.CreateItemParam(itemId);
}
// Populate item properties.
if (model.Param.Context != null)
itemParam.Context = model.Param.Context;
if (model.Param.Name != null)
itemParam.Name = model.Param.Name;
if (model.Param.Value != null && StringUtil.ToBool(model.Param.ValueChanged))
itemParam.Value = model.Param.Value;
if (model.Param.Notes != null)
itemParam.Notes = model.Param.Notes;
if (model.Param.StatusCode != null)
itemParam.Status = model.Param.GetStatus();
// Store item.
if (!SysId.IsValid(itemParam.Id))
{
if (!ControlItemMgr.InsertItemParam(itemParam, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to insert Item Param: " + ControlItemMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlItemMgr.ErrorStateMessage));
}
}
else
{
if (!ControlItemMgr.UpdateItemParam(itemParam, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to update Item Param [Id:" + SysId.ToString(itemParam.Id) + "]: " + ControlItemMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlItemMgr.ErrorStateMessage));
}
}
// Construct response.
PopulateParamResponseItemData(model, responseData.Param, itemParam);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Web.Control.Models.Param;
namespace Sarand.BusinessMgr.Web.Control.Controllers.Param
{
//======================================================================
public partial class ParamController : SbmController_Control
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public ParamController(SbmLocaliser localiser, ILogger<ParamController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[Route("Param/Param")]
public IActionResult Index()
{
if (!ValidUserSession)
AutoLoginUser();
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
return RedirectToAction("", "Security");
}
// Param page.
Model_Param model = new Model_Param(Localiser);
return View("Views/Param/Param/Index.cshtml", model);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -1,35 +1,265 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using NPOI.SS.Formula.Functions;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Control.App;
using Sarand.BusinessMgr.Web.Control.App;
using Sarand.BusinessMgr.Web.Control.Consoles;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
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 System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Database;
using System.Net.Mail;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
namespace Sarand.BusinessMgr.Web.Control.Controllers
{
//======================================================================
public class SbmController_Control : SbmController_Web
{
//-------------------------------------------------------------------
// Data members.
//======================================================================
public class SbmController_Control : SbmController_Web
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Protected construction.
protected SbmController_Control(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Control> logger) : base(localiser)
{
}
//-------------------------------------------------------------------
// Protected construction.
protected SbmController_Control(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Control> logger) : base(localiser)
{
}
//-------------------------------------------------------------------
// Session data.
//-------------------------------------------------------------------
/// <summary>Online session data</summary>
protected SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
protected override SbmSessionData_Web DeserializeSessionData(String sessionDataJson)
{
return JsonConvert.DeserializeObject<SbmSessionData_Control>(sessionDataJson, SbmSessionDataJsonSerializerSettings);
}
//-------------------------------------------------------------------
// Session data.
//-------------------------------------------------------------------
/// <summary>Control session data</summary>
protected SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
public override ISbmUserContext_Core UserContext_Core { get { return new SbmUserContext_Control(SessionData_Control); } }
protected override SbmSessionData_Web DeserializeSessionData(String sessionDataJson)
{
return JsonConvert.DeserializeObject<SbmSessionData_Control>(sessionDataJson, SbmSessionDataJsonSerializerSettings);
}
//-------------------------------------------------------------------
}
//======================================================================
//-------------------------------------------------------------------
// Globals.
//-------------------------------------------------------------------
public SbmEmailConsole_Control EmailConsole_Control { get { return Internals.EmailConsole as SbmEmailConsole_Control; } }
//-------------------------------------------------------------------
// 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(SbmcUserAccount userAccount, bool keepLoggedIn, ref String errorMessage)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Validate User Account, just in case - should have been done before arriving here!
if (userAccount == null)
{
SaLogger.Error($"{SysUtil.Fn} Unable to sign account in - null user account.");
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
if (!userAccount.IsStatusActive)
{
SaLogger.Error($"{SysUtil.Fn} Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
// 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
userAccount.KeepLoggedIn = keepLoggedIn;
userAccount.LastLoginAt = DateTime.Now;
userAccount.InvalidLoginCount = 0;
userAccount.LastInvalidLoginAt = null;
if (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to update User Account for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
SaLogger.Info($"{SysUtil.Fn} User session opened for " + userAccount.EmailAddress, true);
// Create session.
OpenUserSession(SessionContext, userAccount);
// Send the 'Keep Logged In' cookie if requested.
if (userAccount.KeepLoggedIn)
SetKeepLoggedInCookie();
}
return true;
}
//-------------------------------------------------------------------
/// <summary>Start a new user session on sign-in.</summary>
protected bool OpenUserSession(SbmSessionContext sessionContext, SbmcUserAccount userAccount)
{
// 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 SbmSessionData_Control(sessionContext, userAccount));
}
protected void CloseUserSession()
{
ClearSessionData();
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
/// <summary>
/// Check for 'Keep Logged In' cookie and auto-login user if details are valid.
/// </summary>
/// <returns></returns>
protected bool AutoLoginUser()
{
// NB Only allowed if there isn't a current user session.
if (ValidUserSession) return true;
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed) return false;
// Extract cookie details.
String encryptedParams = GetRequestCookie(KeepLoggedIn.CookieName);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (keepLoggedInParams.IsValid())
{
using (SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId))
{
if (userAccount != null)
{
if (userAccount.IsStatusActive)
{
String errorMessage = "";
if (SignUserIn(userAccount, true, ref errorMessage))
{
// Successful login.
SaLogger.Info($"{SysUtil.Fn} Keep Logged In cookie found." + logSuffix);
return true;
}
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
}
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but UserAccount is not active." + logSuffix);
}
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but UserAccount not found." + logSuffix);
}
}
ClearKeepLoggedInCookie();
return false;
}
//-------------------------------------------------------------------
/// <summary>Keep-Logged-In cookie.</summary>
protected void SetKeepLoggedInCookie()
{
String keepLoggedInCookieValue = GenerateKeepLoggedInCookieValue();
int cookieExpiresDays = Math.Max(Sbm.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Min, Math.Min(Sbm.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Max, AppConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays));
SetResponseCookie(KeepLoggedIn.CookieName, keepLoggedInCookieValue, DateTime.Now.AddDays(cookieExpiresDays), true);
SaLogger.Debug($"{SysUtil.Fn} '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(KeepLoggedIn.CookieName))) return;
// Clear cookie by setting expiry date in the past.
SetResponseCookie(KeepLoggedIn.CookieName, "", DateTime.Today.AddDays(-7), true);
SaLogger.Info($"{SysUtil.Fn} '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.
public static class KeepLoggedIn
{
public static String CookieName = "BCKLI";
public static String Base64Key = SacsEncryptionHelper.EncodePlainKey("$BusinessMgr+KeEp-1OgG3d=1n}*32?", SacsEncryptionHelper.KeyLength);
public static String Base64IV = "fpHMZchDkrJzgjVMrjpdHg==";
//public static String Base64IV_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(KeepLoggedIn.Base64Key);
}
//-------------------------------------------------------------------
// 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_Control.User.UserAccountId));
return EncryptParameters(keepLoggedInParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
}
//-------------------------------------------------------------------
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;
}
/// <summary>Verifiy that the account details are populated.</summary>
public bool IsValid()
{
return
SysId.IsValid(UserAccountId);
}
}
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
{
KeepLoggedInParams keepLoggedInParams = new KeepLoggedInParams();
if (!StringUtil.IsEmpty(encryptedParams))
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
keepLoggedInParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId)));
}
if (DebugMode) SaLogger.Info($"{SysUtil.Fn} UserAccountId: " + SysId.ToString(keepLoggedInParams.UserAccountId));
return keepLoggedInParams;
}
//-------------------------------------------------------------------
// Parameter encryption, eg. for images.
//-------------------------------------------------------------------
// Encryption key is the same for all users each time the application is started,
// but a different IV value is generated for each session so that the links/parameters cannot be shared.
public static String Base64Key_ControlApp { get; } = SacsEncryptionHelper.EncodePlainKey("BusinessMgr=Control&Encryption-" + DateTime.Today.Format("ddMMyy"), SacsEncryptionHelper.KeyLength);
protected String EncryptSessionParams(NameValueCollection parameters)
{
return HttpUtility.UrlEncode(EncryptParameters(parameters, SessionData_Control.Base64IV_ControlApp, Base64Key_ControlApp));
}
public static String EncryptSessionParams(SbmController_Control controller, NameValueCollection parameters)
{
return controller.EncryptSessionParams(parameters);
}
protected NameValueCollection ExtractSessionParams(String encryptedParams)
{
return DecryptParameters(encryptedParams, SessionData_Control.Base64IV_ControlApp, Base64Key_ControlApp);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -0,0 +1,484 @@
using Microsoft.AspNetCore.Mvc;
using NPOI.SS.Formula.Functions;
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.Helpers;
using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Security.SignIn;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
using System;
using Sarand.Net.Standard.Base.Utils;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Web.Control.Models;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Web.Control.Helpers;
namespace Sarand.BusinessMgr.Web.Control.Controllers
{
//=======================================================================
partial class SecurityController
{
//-------------------------------------------------------------------
// Sign-in request.
//-------------------------------------------------------------------
private class AjaxResponse_Signin : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Signin(SbmModel_Control model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/Signin")]
public IActionResult Ajax_Signin(Model_Security model)
{
AjaxResponse_Signin responseData = new AjaxResponse_Signin(model);
String messageTitle = Localiser["signin.caption"];
// Validate Sign-in data.
model.ValidateSignInData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Get user account.
SbmcUserAccount userAccount = GetUserAccountForSignin(model, responseData);
disposables.Add(userAccount);
if (responseData.HasControlErrors())
return Json(responseData);
String errorMessage = "";
if (!SignUserIn(userAccount, model.KeepMeSignedIn, ref errorMessage))
{
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
}
// Construct response.
responseData.Success = SessionData_Control?.ValidUser ?? false;
return Json(responseData);
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
private class AjaxResponse_KeepLoggedIn : AjaxResponse_Base
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/KeepLoggedIn")]
public ActionResult Ajax_KeepLoggedIn(Model_Security model)
{
AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return Json(responseData.SetGoHome());
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
return Json(responseData);
// Resend the cookie to extend the expiry date if it is currently populated.
if (!StringUtil.IsEmpty(GetRequestCookie(KeepLoggedIn.CookieName)))
SetKeepLoggedInCookie();
// Construct response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
private SbmcUserAccount GetUserAccountForSignin(Model_Security model, AjaxResponse_Data responseData)
{
SbmcUserAccount userAccount = null;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Identify user account.
SbmcUserAccount.Params prmUserAccount = new SbmcUserAccount.Params();
prmUserAccount.EmailAddress = model.EmailAddress;
prmUserAccount.StatusCat = SbmcUserAccount.StatusCat.Any;
List<SbmcUserAccount> userAccounts = ControlAccountMgr.GetUserAccountList(prmUserAccount);
disposables.AddRange(userAccounts);
foreach (SbmcUserAccount userAccountCandidate in userAccounts)
{
// Account must be active to allow sign-in.
// NB Debug testing allows 'Awaiting' accounts to sign-in.
if ((DebugMode && userAccountCandidate.Status == SaStatus.Awaiting) || userAccountCandidate.IsStatusActive)
{
userAccount = userAccountCandidate;
break;
}
SaLogger.Warn($"{SysUtil.Fn} Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SacsSystem.GetStatusDesc(userAccount.Status), true);
}
if (userAccount != null)
{
// Validate password.
if (!SbmcPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti")))
{
recordInvalidLoginAttempt(model, userAccount);
SaLogger.Warn($"{SysUtil.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"]);
}
return userAccount;
}
private void recordInvalidLoginAttempt(Model_Security model, SbmcUserAccount userAccount)
{
// Record invalid login attempt.
userAccount.InvalidLoginCount++;
userAccount.LastInvalidLoginAt = DateTime.Now;
if (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
SaLogger.Warn($"{SysUtil.Fn} Failed to store invalid sign-in attempt against User Account for " + model.EmailAddress + ": " + ControlAccountMgr.ErrorStateMessage, true);
}
//-------------------------------------------------------------------
// Sign-out request.
//-------------------------------------------------------------------
private class AjaxResponse_SignOut : AjaxResponse_Data
{
// Construction.
public AjaxResponse_SignOut(SbmModel_Control model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/SignOut")]
public ActionResult Ajax_SignOut(Model_Security model)
{
AjaxResponse_SignOut responseData = new AjaxResponse_SignOut(model);
String messageTitle = Localiser["signin.caption.signout"];
// User must have a valid session.
if (!ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve user account - in case sign-out needs to be recorded.
SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(SessionData_Control.User.UserAccountId);
disposables.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn($"{SysUtil.Fn} System error on sign-out - failed to retrieve account details. Id: " + SysId.ToInt(SessionData_Control.User.UserAccountId));
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Clear user session.
CloseUserSession();
SaLogger.Info($"{SysUtil.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.
//-------------------------------------------------------------------
private class AjaxResponse_PasswordScore : AjaxResponse_Base
{
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 = SbmcPasswordHelper.GetPasswordStrength(model.Password);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Change Password request.
//-------------------------------------------------------------------
private class AjaxResponse_ChangePassword : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ChangePassword(SbmModel_Control model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/StorePassword")]
public ActionResult Ajax_StorePassword(Model_Security model)
{
AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword(model);
String messageTitle = Localiser["security.caption.change.password"];
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// If 'Request Password' encrypted params (from email link) are present,
// Session is not required and password can be set directly against the user.
SbmcUserAccount 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 = ControlAccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId);
disposables.Add(userAccount);
if (requestPasswordParams.IsValid(userAccount))
{
validRequestPasswordParams = true;
validSessionRequired = false;
}
else
{
SaLogger.Warn($"{SysUtil.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_Control != null)
{
userAccount = ControlAccountMgr.GetUserAccountById(SessionData_Control.User.UserAccountId);
disposables.Add(userAccount);
}
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 = SbmcPasswordHelper.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 (SbmcPasswordHelper.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 (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to change password for " + userAccount.EmailAddress + ": " + ControlAccountMgr.ErrorStateMessage);
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
}
SaLogger.Info($"{SysUtil.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.
//-------------------------------------------------------------------
private class AjaxResponse_RequestPassword : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RequestPassword(SbmModel_Control model) : base(model) { }
/// <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)
{
AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model);
String messageTitle = Localiser["caption.request.password"];
// Validate data.
model.ValidateRequestPasswordData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve user account.
SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountByEmailAddress(model.EmailAddress);
disposables.Add(userAccount);
if (userAccount == null)
SaLogger.Warn($"{SysUtil.Fn} Unable to send password request email to " + model.EmailAddress + " - account does not exist");
else if (!userAccount.IsStatusActive)
SaLogger.Warn($"{SysUtil.Fn} Unable to send password request email to " + model.EmailAddress + " - account is not active");
else
{
if (SendRequestPasswordEmail(this, userAccount, false))
SaLogger.Info($"{SysUtil.Fn} Password request email sent to " + model.EmailAddress, true);
else SaLogger.Warn($"{SysUtil.Fn} Failed to send password request email to " + model.EmailAddress);
}
}
// Construct response.
responseData.Success = true;
responseData.SuccessMsg = Localiser["security.msg.request.password.email.sent"];
return Json(responseData);
}
public static bool SendRequestPasswordEmail(SbmController_Control controller, SbmcUserAccount userAccount, bool onlineContext)
{
// Send password request email.
SaEmailData emailData = new SaEmailData();
emailData.Config = controller.EmailConfig;
emailData.AddressesTo.Add(userAccount.EmailAddress);
emailData.Subject = controller.Localiser["caption.request.password"];
PopulateRequestPasswordEmailBody(controller, emailData, userAccount, onlineContext);
return controller.EmailConsole.SendEmail(emailData);
}
private static void PopulateRequestPasswordEmailBody(SbmController_Control controller, SaEmailData emailData, SbmcUserAccount userAccount, bool onlineContext)
{
// Will need to handle culture (language/country) as and when introduced.
emailData.MessageBody = SbmcResourceHelper.GetResourceText(SbmcResourceHelper.TextResource.Email_RequestPassword_Html);
emailData.HtmlBody = true;
// Replace dynamic tags.
controller.EmailConsole_Control.ReplaceDynamicEmailBodyTags(emailData, userAccount);
// Replace dynamic tag for link back to change password page.
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.RequestPassword]", GenerateRequestPasswordLink(controller, userAccount.Id, userAccount.EmailAddress, "", onlineContext));
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.ExpiresMinutes]", GetRequestPasswordEmailLinkExpiresMinutes(controller).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_Control { get; } = SacsEncryptionHelper.EncodePlainKey("{~Saxonite@@Request^Password=}", SacsEncryptionHelper.KeyLength);
//private static String Base64IV_RequestPassword_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(Base64Key_RequestPassword_Control);
private static String Base64IV_RequestPassword_Control { get; } = "LenqRMluM6NIOCz97uKCZQ==";
/// <summary>
/// Generate Request Password link.
/// <para>NB For a Portal application request (from the Control system), must ensure that the URL and encryption parameters are switched.</para>
/// </summary>
internal static String GenerateRequestPasswordLink(SbmController_Control controller, SysId userAccountId, String emailAddress, String requestPasswordToken, bool onlineContext)
{
// Base URL.
String baseUrl = onlineContext ? controller.AppConfig.URL.Portal.Root : controller.GetRedirectLocation(RedirectLocation.WebAppBase);
String requestPassword_Base64Key = onlineContext ? Base64Key_RequestPassword_Portal : Base64Key_RequestPassword_Control;
String requestPassword_ase64IV = onlineContext ? Base64IV_RequestPassword_Portal : Base64IV_RequestPassword_Control;
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(controller)), DateUtil.Format_XmlDateTime));
parameters.Add(ParamName_Token, requestPasswordToken);
String link =
baseUrl + "Security/ChangePassword?" +
ParamName_RequestPasswordEncrypted + "=" + HttpUtility.UrlEncode(EncryptParameters(parameters, requestPassword_ase64IV, requestPassword_Base64Key));
return link;
}
internal static int GetRequestPasswordEmailLinkExpiresMinutes(SbmController_Control controller)
{
int expiresMinutes = controller.AppConfig.Security.SignIn.PasswordRequest.EmailLinkExpiresMins;
int expiresMinutes_Min = Sbm.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Min;
int expiresMinutes_Max = Sbm.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Max;
return Math.Max(expiresMinutes_Min, Math.Min(expiresMinutes_Max, expiresMinutes));
}
private 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(SbmcUserAccount 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_Control, Base64Key_RequestPassword_Control);
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,133 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NPOI.SS.Formula.Functions;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Web.Control.Models;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Web.Control.Controllers
{
//======================================================================
public partial class SecurityController : SbmController_Control
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SecurityController(SbmLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
public IActionResult Index()
{
return RedirectToAction("Signin", "Security");
}
//-------------------------------------------------------------------
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 (SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId))
{
if (userAccount != null)
{
if (userAccount.IsStatusActive)
{
if (requestPasswordParams.IsValid(userAccount))
{
model.rpe = encryptedParams;
validSessionRequired = false;
}
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. " + logSuffix);
}
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not active. " + logSuffix);
}
else
{
// User Account not found - redirect to Portal application.
String redirectParamName = ParamName_RequestPasswordEncrypted + "2";
SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not 'Admin'. " + logSuffix);
if (!Request.Query.Keys.Contains(redirectParamName) && !StringUtil.IsEmpty(AppConfig.URL.Portal.Root))
{
UriBuilder uri = new UriBuilder(Regex.Replace(AppConfig.URL.Portal.Root, "/$", "") + "/Security/ChangePassword");
uri.Query = ParamName_RequestPasswordEncrypted + "=" + Uri.EscapeDataString(encryptedParams) + "&" + redirectParamName;
return Redirect(uri.Uri.OriginalString);
}
}
}
}
}
// 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,28 @@
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.Email;
namespace Sarand.BusinessMgr.Web.Control.Helpers
{
//=======================================================================
public static class SbmcEmailConfigHelper
{
//-------------------------------------------------------------------
/// <summary>
/// Replace dynamic tags in email message body.
/// <para></para>
/// </summary>
public static void ReplaceDynamicEmailBodyTags(SaEmailData emailData, SbmSessionData sessionData, SbmcUserAccount userAccount)
{
// Replace core tags, eg. main stylesheet.
// ToDo. EmailConsole.ReplaceDynamicEmailTags(emailData);
// Replace tags known to the web application.
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,139 @@
using Microsoft.AspNetCore.Identity;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Sarand.Net.Standard.Base.Helpers
{
//=======================================================================
public class SbmcPasswordHelper
{
//-------------------------------------------------------------------
// Configuration parameters.
public class PasswordParams
{
public int MinLength = 8;
public int MinLengthNoComplexity = 12;
}
public static PasswordParams Params { get; } = new PasswordParams();
//-------------------------------------------------------------------
/// <summary>Validate password against parameters.</summary>
public static bool IsValidPassword(String password)
{
return GetPasswordStrength(password) >= 4;
}
/// <summary>
/// Assess the password strength - 4+ is valid.
/// <para>0 - Blank</para>
/// <para>1 - &lt; min length - 2 [red]</para>
/// <para>1 - &lt; min length and only 1 character type [red]</para>
/// <para>2 - &lt; min length and only 2 character types [red]</para>
/// <para>2 - &lt; min length without complexity - 2 and only 1 character type [red]</para>
/// <para>3 - &lt; min length, min length with 3 character types [amber]</para>
/// <para>3 - Min length without complexity - 2 [amber]</para>
/// <para>3 - Other invalid passwords [amber]</para>
/// <para>4 - Min length with 4 character types [green]</para>
/// <para>4 - Min length without complexity [green]</para>
/// <para>5 - Min lenth + 2 with 4 character types [green]</para>
/// <para>5 - Min length without complexity with 3 character types [green]</para>
/// </summary>
public static int GetPasswordStrength(String password)
{
String fn = "PasswordHelper.GetPasswordStrength";
// 0 - Blank.
if (StringUtil.IsEmpty(password)) return 0;
// Count number of different types of character.
int characterTypeCount = 0;
try
{
if (Regex.Matches(password, "[A-Z]").Count > 0) characterTypeCount++;
if (Regex.Matches(password, "[a-z]").Count > 0) characterTypeCount++;
if (Regex.Matches(password, "[0-9]").Count > 0) characterTypeCount++;
if (Regex.Matches(password, "[^a-zA-Z0-9]").Count > 0) characterTypeCount++;
}
catch (Exception exc)
{
SaLogger.Debug(fn + " " + ExceptionUtil.GetLogMessage(exc));
}
int passwordLength = StringUtil.GetLength(password);
// 1 - Less than min length - 2;
if (passwordLength < Params.MinLength - 2)
return 1;
// 1 - Less than min length and only 1 character type.
if (passwordLength < Params.MinLength && characterTypeCount == 1)
return 1;
// 2 - Less than min length and only 2 character types.
if (passwordLength < Params.MinLength && characterTypeCount == 2)
return 2;
// 2 - Less than min length without complexity - 2 and only 1 character type.
if (passwordLength < Params.MinLengthNoComplexity - 2 && characterTypeCount == 1)
return 2;
// 3 - Less than minimum length.
if (passwordLength < Params.MinLength)
return 3;
// 3 - Minimum length with only 3 character types.
if (passwordLength >= Params.MinLength && passwordLength < Params.MinLengthNoComplexity && characterTypeCount == 3)
return 3;
// 3 - Minimum length without complexity - 2 with less than 4 character types.
if (passwordLength >= Params.MinLengthNoComplexity - 2 && passwordLength < Params.MinLengthNoComplexity && characterTypeCount < 4)
return 3;
// 3 Catch-all - ensure only valid passwords score 4 or more.
if (passwordLength < Params.MinLengthNoComplexity && characterTypeCount < 4)
return 3;
// 4 - Min length with 4 character types.
if (passwordLength < Params.MinLengthNoComplexity)
return 4;
// 4 - Min length without complexity.
if (passwordLength >= Params.MinLengthNoComplexity && passwordLength < Params.MinLengthNoComplexity + 2 && characterTypeCount < 4)
return 4;
// 5 - Anything more complex.
return 5;
}
//-------------------------------------------------------------------
private static PasswordHasher<SbmcUserAccount> passwordHasher = new PasswordHasher<SbmcUserAccount>();
/// <summary>Get the hashed value that corresponds to the password.</summary>
public static String GetPasswordHash(SbmcUserAccount userAccount, String password)
{
return passwordHasher.HashPassword(userAccount, password);
}
/// <summary>Verify password against hashed value stored against user account.</summary>
public static bool VerifyHashedPassword(SbmcUserAccount userAccount, String password)
{
return passwordHasher.VerifyHashedPassword(userAccount, userAccount.PasswordHashed, password) != PasswordVerificationResult.Failed;
}
/// <summary>Verify password against hashed value with user account as reference.</summary>
public static bool VerifyHashedPassword(SbmcUserAccount userAccount, String password, String passwordHashed)
{
return passwordHasher.VerifyHashedPassword(userAccount, passwordHashed, password) != PasswordVerificationResult.Failed;
}
/// <summary>Check whether the password needs rehashing.</summary>
public static bool PasswordRequiresRehash(SbmcUserAccount userAccount, String password)
{
return passwordHasher.VerifyHashedPassword(userAccount, userAccount.PasswordHashed, password) == PasswordVerificationResult.SuccessRehashNeeded;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,89 @@
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Helpers;
using System.Reflection;
using System;
using Sarand.Net.Standard.Base.Utils;
namespace Sarand.BusinessMgr.Web.Control.Helpers
{
//=======================================================================
public static class SbmcResourceHelper
{
//-------------------------------------------------------------------
// Definitions.
public enum TextResource
{
// Email stylesheets.
Email_Main_Css,
// Email partial content.
Email_Header_Html,
Email_Footer_Html,
// Email templates.
Email_RequestPassword_Html,
}
public enum ImageResource
{
}
//-------------------------------------------------------------------
// Properties.
public static Assembly Assembly { get { return typeof(SacsResourceHelper).GetTypeInfo().Assembly; } }
//-------------------------------------------------------------------
// Text resources.
public static String GetResourceText(TextResource textResource)
{
String partialPath = "";
String resourceFilename = "";
switch (textResource)
{
// Email stylesheet.
case TextResource.Email_Main_Css:
resourceFilename = "sbmc-email-main.css";
break;
// Email partial content.
case TextResource.Email_Header_Html:
resourceFilename = "sbmc-email-header.html";
break;
case TextResource.Email_Footer_Html:
resourceFilename = "sbmc-email-footer.html";
break;
// Email templates.
case TextResource.Email_RequestPassword_Html:
resourceFilename = "sbmc-email-request-password.html";
break;
// Data.
}
if (!StringUtil.IsEmpty(resourceFilename))
{
String resourceText = SacsResourceHelper.GetManifestResourceAsText(Assembly, partialPath, resourceFilename);
if (!StringUtil.IsEmpty(resourceText))
return resourceText;
}
return "";
}
//-------------------------------------------------------------------
// Image resources.
public static byte[] GetResourceImage(ImageResource imageResource)
{
String partialPath = "";
String resourceFilename = "";
switch (imageResource)
{
default:
break;
}
if (!StringUtil.IsEmpty(resourceFilename))
{
byte[] resourceData = SacsResourceHelper.GetManifestResourceAsBinary(Assembly, partialPath, resourceFilename);
if (resourceData != null)
return resourceData;
}
return new byte[0];
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,189 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
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 System;
using System.Diagnostics.Contracts;
namespace Sarand.BusinessMgr.Web.Control.Models.Account
{
//=======================================================================
public class Model_UserAccount : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_UserAccount(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public string Search { get; set; }
public string EmailAddress { get; set; }
public string StatusCat { get; set; }
public SbmcUserAccount.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcUserAccount.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class UserAccountData
{
public String Exists { get; set; }
public string Id { get; set; }
public string EmailAddress { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string Initials { get; set; }
public string LastName { get; set; }
public string PhoneMain { get; set; }
public string PhoneMobile { get; set; }
public string PhoneAlt { get; set; }
public string StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public UserAccountData User { get; } = new UserAccountData();
// User emails.
public string UserEmailAcountActivationPortal { get; set; }
public string UserEmailRequestPasswordPortal { get; set; }
public string UserEmailRequestPasswordControl { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// User.
public bool ValidateUserData(Sacs.Web.Components.SaController.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// User email.
if (User.EmailAddress != null)
{
string controlName = "UserEmailAddress";
User.EmailAddress = User.EmailAddress.ToLowerInvariant();
responseData.SetControlValue(controlName, User.EmailAddress);
if (User.EmailAddress.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(User.EmailAddress) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress)]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(User.EmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
else
{
// Check for duplicate email address.
SbmcUserAccount.Params prmUserAccount = new SbmcUserAccount.Params();
SysId userAccountId = User.Id.ToSysId();
if (SysId.IsValid(userAccountId))
prmUserAccount.IdsExcluded.Add(userAccountId);
prmUserAccount.EmailAddress = User.EmailAddress;
prmUserAccount.StatusCat = SbmcUserAccount.StatusCat.Any;
if (ControlAccountMgr.GetUserAccountCount(prmUserAccount) > 0)
responseData.SetControlError(controlName, Localiser["msg.validation.duplicate.value"]);
}
}
// User title.
if (User.Title != null)
{
string controlName = "UserTitle";
responseData.SetControlValue(controlName, User.Title);
if (StringUtil.GetLength(User.Title) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Title))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Title)]);
}
// User first name.
if (User.FirstName != null)
{
string controlName = "UserFirstName";
responseData.SetControlValue(controlName, User.FirstName);
if (User.FirstName.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(User.FirstName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName)]);
}
// User title.
if (User.Initials != null)
{
string controlName = "UserInitials";
responseData.SetControlValue(controlName, User.Initials);
if (StringUtil.GetLength(User.Initials) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Initials))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Initials)]);
}
// User last name.
if (User.LastName != null)
{
string controlName = "UserLastName";
responseData.SetControlValue(controlName, User.LastName);
if (User.LastName.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(User.LastName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_LastName)]);
}
// Phone.
if (User.PhoneMain != null)
{
string controlName = "UserPhoneMain";
responseData.SetControlValue(controlName, User.PhoneMain);
if (StringUtil.GetLength(User.PhoneMain) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain)]);
else if (!User.PhoneMain.IsEmpty() && !SacsDataValidationHelper.IsValidPhone(User.PhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Mobile Phone.
if (User.PhoneMobile != null)
{
string controlName = "UserPhoneMobile";
responseData.SetControlValue(controlName, User.PhoneMobile);
if (StringUtil.GetLength(User.PhoneMobile) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile)]);
else if (!User.PhoneMobile.IsEmpty() && !SacsDataValidationHelper.IsValidPhone(User.PhoneMobile))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Alt Phone.
if (User.PhoneAlt != null)
{
string controlName = "UserPhoneAlt";
responseData.SetControlValue(controlName, User.PhoneAlt);
if (StringUtil.GetLength(User.PhoneAlt) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt)]);
else if (!User.PhoneAlt.IsEmpty() && !SacsDataValidationHelper.IsValidPhone(User.PhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// User status.
if (User.StatusCode != null)
{
string controlName = "UserStatus";
if (User.StatusCode.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcUserAccount.ValidStatuses.Contains(User.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -1,6 +1,6 @@
using System;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Models
namespace Sarand.BusinessMgr.Web.Control.Models
{
public class ErrorViewModel
{
@@ -0,0 +1,141 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
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 System;
namespace Sarand.BusinessMgr.Web.Control.Models.Instance
{
//=======================================================================
public class Model_Owner : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Owner(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public String Search { get; set; }
public String StatusCat { get; set; }
public SbmcOwner.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcOwner.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class OwnerData
{
public String Exists { get; set; }
public String Id { get; set; }
public String Name { get; set; }
public String ContactName { get; set; }
public String ContactEmailAddress { get; set; }
public String ContactPhoneMain { get; set; }
public String ContactPhoneAlt { get; set; }
public String StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public OwnerData Owner { get; } = new OwnerData();
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Owner.
public bool ValidateOwnerData(SbmController_Base.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Owner name.
if (Owner.Name != null)
{
String controlName = "OwnerName";
responseData.SetControlValue(controlName, Owner.Name);
if (StringUtil.IsBlank(Owner.Name))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Owner.Name) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_Name))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_Name)]);
}
// Owner contact name.
if (Owner.ContactName != null)
{
String controlName = "OwnerContactName";
responseData.SetControlValue(controlName, Owner.ContactName);
if (!StringUtil.IsBlank(Owner.ContactName))
{
if (StringUtil.GetLength(Owner.ContactName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactName)]);
}
}
// Contact email.
if (Owner.ContactEmailAddress != null)
{
String controlName = "OwnerContactEmailAddress";
Owner.ContactEmailAddress = Owner.ContactEmailAddress.ToLowerInvariant();
responseData.SetControlValue(controlName, Owner.ContactEmailAddress);
if (!StringUtil.IsBlank(Owner.ContactEmailAddress))
{
if (StringUtil.GetLength(Owner.ContactEmailAddress) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactEmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactEmailAddress)]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(Owner.ContactEmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
// Contact Phone.
if (Owner.ContactPhoneMain != null)
{
String controlName = "OwnerContactPhoneMain";
responseData.SetControlValue(controlName, Owner.ContactPhoneMain);
if (StringUtil.GetLength(Owner.ContactPhoneMain) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneMain)]);
else if (!StringUtil.IsEmpty(Owner.ContactPhoneMain) && !SacsDataValidationHelper.IsValidPhone(Owner.ContactPhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Contact Alt Phone.
if (Owner.ContactPhoneAlt != null)
{
String controlName = "OwnerContactPhoneAlt";
responseData.SetControlValue(controlName, Owner.ContactPhoneAlt);
if (StringUtil.GetLength(Owner.ContactPhoneAlt) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneAlt)]);
else if (!StringUtil.IsEmpty(Owner.ContactPhoneAlt) && !SacsDataValidationHelper.IsValidPhone(Owner.ContactPhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Owner status.
if (Owner.StatusCode != null)
{
String controlName = "OwnerStatus";
if (StringUtil.IsBlank(Owner.StatusCode))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcOwner.ValidStatuses.Contains(Owner.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,171 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
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 System;
namespace Sarand.BusinessMgr.Web.Control.Models.Instance
{
//=======================================================================
public class Model_Repository : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Repository(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public String Search { get; set; }
public String StatusCat { get; set; }
public SbmcRepository.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcRepository.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class RepositoryData
{
public String Exists { get; set; }
public String Id { get; set; }
public String OwnerId { get; set; }
public String Context { get; set; }
public String ServerName { get; set; }
public String InstanceName { get; set; }
public String DatabaseName { get; set; }
public String UserName { get; set; }
public String Password { get; set; }
public String StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public RepositoryData Repository { get; } = new RepositoryData();
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Repository.
public bool ValidateRepositoryData(SbmController_Base.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Repository Owner.
if (Repository.OwnerId != null)
{
String controlName = "RepositoryOwner";
SysId ownerId = Repository.OwnerId.ToSysId();
if (!SysId.IsValid(ownerId))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else
{
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
if (owner == null)
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
// Repository Context.
if (Repository.Context != null)
{
String controlName = "RepositoryContext";
responseData.SetControlValue(controlName, Repository.Context);
if (StringUtil.IsBlank(Repository.Context))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Repository.Context) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Context))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Context)]);
else
{
// Check for duplicate context.
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
SysId repositoryId = Repository.Id.ToSysId();
if (SysId.IsValid(repositoryId))
prmRepository.IdsExcluded.Add(repositoryId);
prmRepository.ContextsIncluded.Add(Repository.Context);
prmRepository.StatusCat = SbmcRepository.StatusCat.Any;
if (ControlInstanceMgr.GetRepositoryCount(prmRepository) > 0)
responseData.SetControlError(controlName, Localiser["msg.validation.duplicate.value"]);
}
}
// Repository Server name.
if (Repository.ServerName != null)
{
String controlName = "RepositoryServerName";
responseData.SetControlValue(controlName, Repository.ServerName);
if (StringUtil.IsBlank(Repository.ServerName))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Repository.ServerName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_ServerName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_ServerName)]);
}
// Repository Instance name.
if (Repository.InstanceName != null)
{
String controlName = "RepositoryInstanceName";
responseData.SetControlValue(controlName, Repository.InstanceName);
if (StringUtil.GetLength(Repository.InstanceName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_InstanceName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_InstanceName)]);
}
// Repository Database name.
if (Repository.DatabaseName != null)
{
String controlName = "RepositoryDatabaseName";
responseData.SetControlValue(controlName, Repository.DatabaseName);
if (StringUtil.IsBlank(Repository.DatabaseName))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Repository.DatabaseName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_DatabaseName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_DatabaseName)]);
}
// Repository User name.
if (Repository.UserName != null)
{
String controlName = "RepositoryUserName";
responseData.SetControlValue(controlName, Repository.UserName);
if (StringUtil.GetLength(Repository.UserName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_UserName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_UserName)]);
}
// Repository password.
if (Repository.Password != null)
{
String controlName = "RepositoryPassword";
responseData.SetControlValue(controlName, Repository.Password);
if (StringUtil.GetLength(Repository.Password) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Password))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Password)]);
}
// Repository status.
if (Repository.StatusCode != null)
{
String controlName = "RepositoryStatus";
if (StringUtil.IsBlank(Repository.StatusCode))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcRepository.ValidStatuses.Contains(Repository.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -1,6 +1,6 @@
using Sarand.BusinessMgr.Net.Core.Web.App;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Models
namespace Sarand.BusinessMgr.Web.Control.Models
{
//=======================================================================
public class Model_Home : SbmModel_Control
@@ -0,0 +1,106 @@
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using System;
namespace Sarand.BusinessMgr.Web.Control.Models
{
//=======================================================================
public class Model_Security : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Security(SbmLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sbc-sec.css");
AdditionalFiles_JS.Add("js/sbc-sec.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(SbmController_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(SbmcUserAccount userAccount, SbmController_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 (!SbmcPasswordHelper.IsValidPassword(Password))
responseData.SetControlError(nameof(PasswordConfirm), Localiser.GetText("msg.warn.password.invalid", new object[] { SbmcPasswordHelper.Params.MinLength, SbmcPasswordHelper.Params.MinLengthNoComplexity }));
}
if (!responseData.HasControlErrors())
{
if (PasswordCurrent != null && !SbmcPasswordHelper.VerifyHashedPassword(userAccount, PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["security.change.password.current.invalid"]);
if (SbmcPasswordHelper.VerifyHashedPassword(userAccount, Password))
responseData.SetControlError(nameof(Password), Localiser["security.change.password.not.changed"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Request Password.
public bool ValidateRequestPasswordData(SbmController_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,153 @@
using DocumentFormat.OpenXml.EMMA;
using DocumentFormat.OpenXml.Spreadsheet;
using Microsoft.AspNetCore.Mvc.Rendering;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.CodeTypes;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace Sarand.BusinessMgr.Web.Control.Models.Param
{
//=======================================================================
public class Model_Param : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Param(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
/// <summary>List of Item Types for dropdown.</summary>
public IEnumerable<SelectListItem> GetParamItemTypesForSelect()
{
List<CodeDesc> codeTypes = new List<CodeDesc>();
foreach (var itemType in SbmcItemParam.ValidItemTypes)
codeTypes.Add(((int)itemType).ToString(), itemType.ToString());
return new SelectList(codeTypes, "Code", "Desc");
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public string ItemType { get; set; }
public string ItemId { get; set; }
public string Search { get; set; }
public string StatusCat { get; set; }
public SbmcItemParam.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcItemParam.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class ItemParamData
{
public String Exists { get; set; }
public string Id { get; set; }
public string ItemType { get; set; }
public string ItemId { get; set; }
public string Context { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string ValueChanged { get; set; }
public string Notes { get; set; }
public string StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public ItemParamData Param { get; } = new ItemParamData();
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
public bool ValidateParamData(Sacs.Web.Components.SaController.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Check whether data suggests that Param exists.
bool paramExists = StringUtil.ToBool(Param.Exists) && SysId.IsValid(Param.Id.ToSysId());
// Item.
if (Param.ItemType != null || Param.ItemId != null)
{
string controlName = "ParamItem";
ItemId itemId = new ItemId(StringUtil.ToShort(Param.ItemType), Param.ItemId.ToSysId());
if (!paramExists && (Param.ItemType.IsBlank() || Param.ItemId.IsBlank()))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.IsBlank(ControlItemMgr.GetItemCaption(itemId)))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Context.
if (Param.Context != null)
{
string controlName = "ParamContext";
responseData.SetControlValue(controlName, Param.Context);
if (Param.Context.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Param.Context) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Context))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Context)]);
}
// Name.
if (Param.Name != null)
{
string controlName = "ParamName";
responseData.SetControlValue(controlName, Param.Name);
if (Param.Name.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Param.Name) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Name))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Name)]);
}
// Value.
if (Param.Value != null)
{
string controlName = "ParamValue";
responseData.SetControlValue(controlName, Param.Value);
if (StringUtil.GetLength(Param.Value) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Value))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Value)]);
}
// Notes.
if (Param.Notes != null)
{
string controlName = "ParamNotes";
responseData.SetControlValue(controlName, Param.Notes);
if (StringUtil.GetLength(Param.Notes) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Notes))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Notes)]);
}
// User status.
if (Param.StatusCode != null)
{
string controlName = "ParamStatus";
if (Param.StatusCode.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcItemParam.ValidStatuses.Contains(Param.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -1,19 +1,55 @@
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Models;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Web.Control.App;
using System;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Models
namespace Sarand.BusinessMgr.Web.Control.Models
{
//=======================================================================
public class SbmModel_Control : SbmModel_Base
{
//-------------------------------------------------------------------
// Protected construction.
protected SbmModel_Control(SbmLocaliser localiser) :
base(localiser)
{
}
//=======================================================================
public class SbmModel_Control : SbmModel_Base
{
//-------------------------------------------------------------------
// Protected construction.
protected SbmModel_Control(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
//-------------------------------------------------------------------
// Properties.
public SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
public override ISbmUserContext_Core UserContext_Core { get { return new SbmUserContext_Control(SessionData_Control); } }
//-------------------------------------------------------------------
/// <summary>
/// Form control value max length - BusinessMgr tables.
/// <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(SbmDbDfn_Control.ColumnIdx column)
{
int maxLength = (int)SbmDbDfn_Control.GetColTextLen(column);
switch (column)
{
case SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress:
return Math.Min(maxLength, 100);
case SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName:
case SbmDbDfn_Control.ColumnIdx.UserAccount_LastName:
return Math.Min(maxLength, 40);
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain:
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile:
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt:
return Math.Min(maxLength, 20);
case SbmDbDfn_Control.ColumnIdx.ItemParam_Value:
if (maxLength == 0 || maxLength > 2048) return 2048;
break;
}
return maxLength;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -126,4 +126,283 @@
<data name="label.name" xml:space="preserve">
<value>Name</value>
</data>
<data name="label.email.address" xml:space="preserve">
<value>Email address</value>
</data>
<data name="label.password" xml:space="preserve">
<value>Password</value>
</data>
<data name="label.keep.signed.in" xml:space="preserve">
<value>Keep signed-in</value>
</data>
<data name="label.forgotten.password" xml:space="preserve">
<value>Forgotten password</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>Signout</value>
</data>
<data name="label.yes" xml:space="preserve">
<value>Yes</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.close" xml:space="preserve">
<value>Close</value>
</data>
<data name="label.cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="info.char.available" xml:space="preserve">
<value>[1 char available]</value>
</data>
<data name="info.chars.available" xml:space="preserve">
<value>[{0} chars available]</value>
</data>
<data name="label.selected.none" xml:space="preserve">
<value>none selected</value>
</data>
<data name="label.selected.count" xml:space="preserve">
<value>{0} selected</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="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.data.refreshing" xml:space="preserve">
<value>Refreshing data. Please wait ...</value>
</data>
<data name="msg.data.querying" xml:space="preserve">
<value>Querying data. Please wait ...</value>
</data>
<data name="msg.data.noneFound" xml:space="preserve">
<value>No matching data has been found.</value>
</data>
<data name="copyright.symbol.year.html" xml:space="preserve">
<value>&amp;copy; {0}</value>
</data>
<data name="msg.validation.mandatory.value" xml:space="preserve">
<value>Mandatory value</value>
</data>
<data name="label.signin" xml:space="preserve">
<value>Signin</value>
</data>
<data name="caption.request.password" xml:space="preserve">
<value>Request Password</value>
</data>
<data name="label.home" xml:space="preserve">
<value>Home</value>
</data>
<data name="label.accounts" xml:space="preserve">
<value>Accounts</value>
</data>
<data name="label.users" xml:space="preserve">
<value>Users</value>
</data>
<data name="label.instances" xml:space="preserve">
<value>Instances</value>
</data>
<data name="label.owners" xml:space="preserve">
<value>Owners</value>
</data>
<data name="label.repositories" xml:space="preserve">
<value>Repositories</value>
</data>
<data name="label.system" xml:space="preserve">
<value>System</value>
</data>
<data name="label.security" xml:space="preserve">
<value>Security</value>
</data>
<data name="label.database" xml:space="preserve">
<value>Database</value>
</data>
<data name="label.logs" xml:space="preserve">
<value>Logs</value>
</data>
<data name="caption.change.password" xml:space="preserve">
<value>Change Password</value>
</data>
<data name="label.signout" xml:space="preserve">
<value>Signout</value>
</data>
<data name="label.user" xml:space="preserve">
<value>User</value>
</data>
<data name="msg.validation.maximum.length" xml:space="preserve">
<value>Value must not exceed {0} characters</value>
</data>
<data name="msg.validation.invalid.value" xml:space="preserve">
<value>Invalid value</value>
</data>
<data name="msg.validation.duplicate.value" xml:space="preserve">
<value>Value is already in use</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.validation.select.option" xml:space="preserve">
<value>At least one option must be selected.</value>
</data>
<data name="label.refresh" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="label.create" xml:space="preserve">
<value>Create</value>
</data>
<data name="label.edit" xml:space="preserve">
<value>Edit</value>
</data>
<data name="label.emails" xml:space="preserve">
<value>Emails</value>
</data>
<data name="label.last.login" xml:space="preserve">
<value>Last login</value>
</data>
<data name="label.search" xml:space="preserve">
<value>Search</value>
</data>
<data name="label.first" xml:space="preserve">
<value>First</value>
</data>
<data name="label.previous" xml:space="preserve">
<value>Previous</value>
</data>
<data name="label.next" xml:space="preserve">
<value>Next</value>
</data>
<data name="label.last" xml:space="preserve">
<value>Last</value>
</data>
<data name="label.page.info" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
<data name="label.status" xml:space="preserve">
<value>Status</value>
</data>
<data name="msg.error.item.id.not.specified" xml:space="preserve">
<value>Item Id not specified</value>
</data>
<data name="label.first.name" xml:space="preserve">
<value>First name</value>
</data>
<data name="label.last.name" xml:space="preserve">
<value>Last name</value>
</data>
<data name="label.save" xml:space="preserve">
<value>Save</value>
</data>
<data name="label.title" xml:space="preserve">
<value>Title</value>
</data>
<data name="label.initials" xml:space="preserve">
<value>Initials</value>
</data>
<data name="label.phone" xml:space="preserve">
<value>Phone</value>
</data>
<data name="label.mobile" xml:space="preserve">
<value>Mobile</value>
</data>
<data name="label.phone.alt" xml:space="preserve">
<value>Alt phone</value>
</data>
<data name="caption.user.create" xml:space="preserve">
<value>Create User</value>
</data>
<data name="caption.user.update" xml:space="preserve">
<value>Update User</value>
</data>
<data name="caption.owner.create" xml:space="preserve">
<value>Create Owner</value>
</data>
<data name="caption.owner.update" xml:space="preserve">
<value>Update Owner</value>
</data>
<data name="caption.repository.create" xml:space="preserve">
<value>Create Repository</value>
</data>
<data name="caption.repository.update" xml:space="preserve">
<value>Update Repository</value>
</data>
<data name="label.owner" xml:space="preserve">
<value>Owner</value>
</data>
<data name="label.repository" xml:space="preserve">
<value>Repository</value>
</data>
<data name="label.contact" xml:space="preserve">
<value>Contact</value>
</data>
<data name="msg.error.system" xml:space="preserve">
<value>System error encountered.</value>
</data>
<data name="label.server" xml:space="preserve">
<value>Server</value>
</data>
<data name="label.instance" xml:space="preserve">
<value>Instance</value>
</data>
<data name="label.item.selected" xml:space="preserve">
<value>Selected item</value>
</data>
<data name="label.items.available" xml:space="preserve">
<value>Available items</value>
</data>
<data name="caption.select.owner" xml:space="preserve">
<value>Select Owner</value>
</data>
<data name="label.context" xml:space="preserve">
<value>Context</value>
</data>
<data name="label.params" xml:space="preserve">
<value>Params</value>
</data>
<data name="label.param" xml:space="preserve">
<value>Param</value>
</data>
<data name="label.value" xml:space="preserve">
<value>Value</value>
</data>
<data name="caption.param.create" xml:space="preserve">
<value>Create Param</value>
</data>
<data name="caption.param.update" xml:space="preserve">
<value>Update Param</value>
</data>
<data name="label.configuration" xml:space="preserve">
<value>Configuration</value>
</data>
<data name="label.privacy" xml:space="preserve">
<value>Privacy</value>
</data>
<data name="label.notes" xml:space="preserve">
<value>Notes</value>
</data>
<data name="label.item" xml:space="preserve">
<value>Item</value>
</data>
<data name="label.item.type" xml:space="preserve">
<value>Item type</value>
</data>
<data name="caption.select.item" xml:space="preserve">
<value>Select Item</value>
</data>
<data name="msg.warn.item.not.selected" xml:space="preserve">
<value>Item must be selected.</value>
</data>
</root>
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Password Request</title>
[#Email.MainStylesheet]
</head>
<body>
[#Email.Header]
<div class="sbc-email-body">
<div class="sbc-email-panel-caption">
<div class="sbc-caption">Password Request</div>
</div>
<div class="sbc-email-panel-content">
<div class="sbc-para">We received a request to re-set your password.</div>
<div class="sbc-para">Follow <span style="font-weight:bold;"><a href="[#Link.RequestPassword]">this link</a></span> to set a new password against your account.</div>
<div class="sbc-para">This link will expire in [#Link.ExpiresMinutes] minutes.</div>
<div class="sbc-para">Thank you.</div>
<div class="sbc-para">Sarand BusinessMgr team</div>
</div>
</div>
[#Email.Footer]
</body>
</html>
@@ -0,0 +1,3 @@
<div class="sax-email-footer">
<div class="sax-content"></div>
</div>
@@ -0,0 +1,3 @@
<div class="sax-email-header">
<div class="sax-content">Sarand BusinessMgr</div>
</div>
@@ -0,0 +1,96 @@
html
{
font-size:10px;
}
body
{
margin: 0;
padding: 0;
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 1.6rem;
line-height: 1.5;
background-color: #fff;
}
td
{
vertical-align: top;
}
.sax-email-header
{
margin: 0 0 12px 0;
border-bottom: 1px solid rgba(0,0,0,.1);
box-shadow: 0 1px 0 rgba(0,0,0,.1);
-moz-box-shadow: 0 1px 0 rgba(0,0,0,.1);
-webkit-box-shadow: 0 1px 0 rgba(0,0,0,.1);
}
.sax-email-header .sax-content, .sax-email-body, .sax-email-footer .sax-content
{
width: 90%;
max-width: 1040px;
margin:0 auto;
padding:12px 24px;
}
.sax-email-header .sax-content
{
font-size:2.3rem;
}
.sax-email-body
{
}
.sax-email-panel-caption
{
margin: 12px 0;
font-size: 3.2rem;
font-weight: bold;
color: #000;
background-color: transparent;
}
.sax-email-panel-content
{
margin: 12px 0;
}
.sax-para
{
margin-bottom: 16px;
}
td.sax-label
{
padding: 4px 12px;
font-size: 1.6rem;
text-align: right;
color: #808080;
white-space: nowrap;
}
td.sax-value
{
padding-top: 4px;
font-size: 1.6rem;
}
.sax-email-footer
{
margin: 24px 0 12px 0;
border-top: 1px rgba(0,0,0,.1) solid;
}
.sax-email-footer .sax-content
{
padding:12px 24px;
font-size:1.2rem;
}
.sax-email-footer .sax-content .sax-footer-lower
{
background-color: #f9f9f9;
}
@@ -3,7 +3,8 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
<RootNamespace>Sarand.BusinessMgr.Net.Core.Web</RootNamespace>
<RootNamespace>Sarand.BusinessMgr.Web</RootNamespace>
<AssemblyName>Sarand.BusinessMgr.Web</AssemblyName>
</PropertyGroup>
<ItemGroup>
@@ -23,7 +24,12 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Control\Consoles\" />
<Folder Include="Control\Controllers\System\" />
<Folder Include="Views\Security\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup>
@@ -49,4 +55,10 @@
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localisation\SbmControlProperties.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,273 @@
@* Account - User Account *@
@model Model_UserAccount
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "User Accounts";
Model.AdditionalFiles_JS.Add("js/sbc-res-pag.js");
Model.AdditionalFiles_JS.Add("js/sbc-usr.js");
}
<div class="sbc-panel Users fullheight">
<script type="text/javascript">
sbc.dlg.title.createItem = '@(Localiser.GetJavascriptText("caption.user.create"))';
sbc.dlg.title.updateItem = '@(Localiser.GetJavascriptText("caption.user.update"))';
</script>
<div class="sbc-panel-data sbc-flex">
@* Filter form - wrap header to include paging controls. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserFilter", @class = "sbc-form", @onSubmit = "return false;" }))
{
<div class="sbc-panel-header">
<div class="row">
<div class="fixed">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="col center"><div class="sbc-panel-caption">@Localiser["label.users"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
@* Filter Panel *@
<div class="sbc-panel-filter">
<div class="row pb4">
<div class="col-sm-5 sbc-label text-left text-sm-right">@Localiser["label.search"]</div>
<div class="col-sm-7">
@Html.TextBoxFor(model => model.Filter.Search, new { @id = "", @class = "FilterSearch", @maxlength = "32" })
</div>
</div>
</div>
}
<div class="sbc-panel-item-table sbc-flex">
<table class="sbc-item-list">
<thead>
<tr>
<th class="pl-2">@Localiser["label.email"]</th>
<th class="">@Localiser["label.name"]</th>
<th class="center">@Localiser["label.phone"]</th>
<th class="center">@Localiser["label.last.login"]</th>
<th class="center">@Localiser["label.status"]</th>
<th class="right" style="width:2%;"></th>
</tr>
<tr><td colspan="6"><div class="sbc-panel-messages"></div></td></tr>
</thead>
<tbody></tbody>
</table>
</div>
@* Element templates *@
<div class="sbc-panel-item-template">
@* Filter Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu UserFilter">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
@* User row template - table is ignored. *@
<table class="">
<tr class="sbc-row-item User">
<td><div class="UserEmailAddress pl-1"></div></td>
<td><div class="UserDisplayName"></div></td>
<td><div class="UserPhone center"></div></td>
<td><div class="UserLastLoginOn center"></div></td>
<td><div class="UserStatusDesc center"></div></td>
<td>
<div class="right">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</td>
</tr>
</table>
@* Item Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu User">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
</div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserId", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.User.Id, new { @id = "", @class = "UserId" })
}
<div id="dlgUser" class="sbc-dialog">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUser", @class = "sbc-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.User.Exists, new { @id = "", @class = "UserExists" })
@Html.HiddenFor(model => model.User.Id, new { @id = "", @class = "UserId" })
@* Email Address *@
<div class="sbc-form-control UserEmailAddress">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserEmailAddress"><span>@Localiser["label.email"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.EmailAddress, new { @id = "ifUserEmailAddress", @class = "UserEmailAddress sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Title *@
<div class="sbc-form-control UserTitle">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserTitle"><span>@Localiser["label.title"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.Title, new { @id = "ifUserTitle", @class = "UserTitle sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Title) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* First Name *@
<div class="sbc-form-control UserFirstName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserFirstName"><span>@Localiser["label.first.name"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.FirstName, new { @id = "ifUserFirstName", @class = "UserFirstName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Initials *@
<div class="sbc-form-control UserInitials">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserInitials"><span>@Localiser["label.initials"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.Initials, new { @id = "ifUserInitials", @class = "UserInitials sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Initials) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Last Name *@
<div class="sbc-form-control UserLastName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserLastName"><span>@Localiser["label.last.name"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.LastName, new { @id = "ifUserLastName", @class = "UserLastName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_LastName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* PhoneMain *@
<div class="sbc-form-control UserPhoneMain">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserPhoneMain"><span>@Localiser["label.phone"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.PhoneMain, new { @id = "ifUserPhoneMain", @class = "UserPhoneMain sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* PhoneMobile *@
<div class="sbc-form-control UserPhoneMobile">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserPhoneMobile"><span>@Localiser["label.mobile"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.PhoneMobile, new { @id = "ifUserPhoneMobile", @class = "UserPhoneMobile sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* PhoneAlt *@
<div class="sbc-form-control UserPhoneAlt">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifUserPhoneAlt"><span>@Localiser["label.phone.alt"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.User.PhoneAlt, new { @id = "ifUserPhoneAlt", @class = "UserPhoneAlt sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* User Status *@
<div class="sbc-form-control UserStatus">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="cbUserStatus"><span>@Localiser["label.status"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
<select id="cbUserStatus" class="sbc-select UserStatus" name="User.StatusCode"></select>
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
</div>
</div>
@@ -0,0 +1,25 @@
@model SbmModel_Control
@inject SbmLocaliser Localiser
@{
// Scripts.
Model.AdditionalFiles_JS.Add("js/sbc-res-pag.js");
}
<script type="text/javascript">
sbc.resultsPaging = sbc.resultsPaging || { fn: {}, msg: {}, contexts: [] };
sbc.resultsPaging.msg.pageInfo = '@Localiser.GetJavascriptText("label.page.info", "{0}", "{1}")';
</script>
@* Results paging *@
<div class="sbc-results-paging hidden">
@Html.HiddenFor(model => model.Paging.PageNo, new { @id = "", @class = "PagingPageNo" })
@Html.HiddenFor(model => model.Paging.PageLength, new { @id = "", @class = "PagingPageLength" })
<div class="iblock sbc-action sbc-paging-first"><img src="~/img/action/first24.png" alt="@(Localiser["label.first"])" title="@(Localiser["label.first"])" /></div>
<div class="iblock sbc-action sbc-paging-prev"><img src="~/img/action/prev24.png" alt="@(Localiser["label.previous"])" title="@(Localiser["label.previous"])" /></div>
<div class="iblock sbc-paging-info"></div>
<div class="iblock sbc-action sbc-paging-next"><img src="~/img/action/next24.png" alt="@(Localiser["label.next"])" title="@(Localiser["label.next"])" /></div>
<div class="iblock sbc-action sbc-paging-last"><img src="~/img/action/last24.png" alt="@(Localiser["label.last"])" title="@(Localiser["label.last"])" /></div>
</div>
@@ -0,0 +1,53 @@
@model SbmModel_Control
@inject SbmLocaliser Localiser
@{
// Ensure view is only included once in page.
if (!Model.RegisterView("Views/Common/SelectItem")) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sbc-sel-itm.js");
}
<link rel="stylesheet" href="~/css/sbc-sel-itm.css?@Model.HrefVersion" />
<div id="dlgSelectItem" class="sbc-dialog sbc-form">
<div class="container-fluid h-100">
@* Selected Item *@
<div class="SelectedItem">
<div class="row">
<div class="col-sm-12"><div class="sbc-label">@Localiser["label.item.selected"]:</div></div>
<div class="col-sm-12"><div class="SelectedItemName"></div></div>
</div>
<div class="spacer2"></div>
</div>
@* Item Filter *@
<div class="ItemFilter sbc-form">
<div class="row">
<div class="col right"><div class="sbc-label">@Localiser["label.search"]</div></div>
<div class="fixed"><input type="search" id="ifItemFilter" class="sbc-input" autocomplete="off" onkeyup="sbc.data.selectItem.fn.displayItems();" /></div>
</div>
<div class="spacer2"></div>
</div>
@* Available items *@
<div class="row">
<div class="col"><div class="sbc-label bottom">@Localiser["label.items.available"]:</div></div>
</div>
<div class="row">
<div class="col-sm-12 sbc-selectItem-items AvailableItems"></div>
</div>
</div>
<div class="sbc-panel-item-template">
<div class="sbc-panel-item Item">
<div class="ItemName"></div>
</div>
</div>
</div>
@@ -1,4 +1,6 @@
@{
@model Model_Home
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Home Page";
}
@@ -0,0 +1,222 @@
@* Instance - Owner *@
@model Model_Owner
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Owner Accounts";
Model.AdditionalFiles_JS.Add("js/sbc-res-pag.js");
Model.AdditionalFiles_JS.Add("js/sbc-ins-own.js");
}
<div class="sbc-panel Owners fullheight">
<script type="text/javascript">
sbc.dlg.title.createItem= '@(Localiser.GetJavascriptText("caption.owner.create"))';
sbc.dlg.title.updateItem= '@(Localiser.GetJavascriptText("caption.owner.update"))';
</script>
<div class="sbc-panel-data sbc-flex">
@* Filter form - wrap header to include paging controls. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOwnerFilter", @class = "sbc-form", @onSubmit = "return false;" }))
{
<div class="sbc-panel-header">
<div class="row">
<div class="fixed">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="col center"><div class="sbc-panel-caption">@Localiser["label.owners"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
@* Filter Panel *@
<div class="sbc-panel-filter">
<div class="row pb4">
<div class="col-sm-5 sbc-label text-left text-sm-right">@Localiser["label.search"]</div>
<div class="col-sm-7">
@Html.TextBoxFor(model => model.Filter.Search, new { @id = "", @class = "FilterSearch", @maxlength = "32" })
</div>
</div>
</div>
}
<div class="sbc-panel-item-table sbc-flex">
<table class="sbc-item-list">
<thead>
<tr>
<th class="pl-2">@Localiser["label.name"]</th>
<th class="">@Localiser["label.contact"]</th>
<th class="">@Localiser["label.email"]</th>
<th class="center">@Localiser["label.phone"]</th>
<th class="center">@Localiser["label.status"]</th>
<th class="right" style="width:2%;"></th>
</tr>
<tr><td colspan="6"><div class="sbc-panel-messages"></div></td></tr>
</thead>
<tbody></tbody>
</table>
</div>
@* Element templates *@
<div class="sbc-panel-item-template">
@* Filter Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu OwnerFilter">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
@* Owner row template - table is ignored. *@
<table class="">
<tr class="sbc-row-item Owner">
<td><div class="OwnerName pl-1"></div></td>
<td><div class="OwnerContactName"></div></td>
<td><div class="OwnerContactEmailAddress"></div></td>
<td><div class="OwnerContactPhone center"></div></td>
<td><div class="OwnerStatusDesc center"></div></td>
<td>
<div class="right">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</td>
</tr>
</table>
@* Item Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu Owner">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
</div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOwnerId", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Owner.Id, new { @id = "", @class = "OwnerId" })
}
<div id="dlgOwner" class="sbc-dialog">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOwner", @class = "sbc-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Owner.Exists, new { @id = "", @class = "OwnerExists" })
@Html.HiddenFor(model => model.Owner.Id, new { @id = "", @class = "OwnerId" })
@* Name *@
<div class="sbc-form-control OwnerName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifOwnerName"><span>@Localiser["label.name"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Owner.Name, new { @id = "ifOwnerName", @class = "OwnerName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_Name) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Contact Name *@
<div class="sbc-form-control OwnerContactName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifOwnerContactName"><span>@Localiser["label.contact"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Owner.ContactName, new { @id = "ifOwnerContactName", @class = "OwnerContactName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Contact Email Address *@
<div class="sbc-form-control OwnerContactEmailAddress">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifOwnerContactEmailAddress"><span>@Localiser["label.email"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Owner.ContactEmailAddress, new { @id = "ifOwnerContactEmailAddress", @class = "OwnerContactEmailAddress sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactEmailAddress) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* PhoneMain *@
<div class="sbc-form-control OwnerContactPhoneMain">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifOwnerContactPhoneMain"><span>@Localiser["label.phone"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Owner.ContactPhoneMain, new { @id = "ifOwnerContactPhoneMain", @class = "OwnerContactPhoneMain sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneMain) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* PhoneAlt *@
<div class="sbc-form-control OwnerContactPhoneAlt">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifOwnerContactPhoneAlt"><span>@Localiser["label.phone.alt"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Owner.ContactPhoneAlt, new { @id = "ifOwnerContactPhoneAlt", @class = "OwnerContactPhoneAlt sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneAlt) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Owner Status *@
<div class="sbc-form-control OwnerStatus">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="cbOwnerStatus"><span>@Localiser["label.status"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
<select id="cbOwnerStatus" class="sbc-select OwnerStatus" name="Owner.StatusCode"></select>
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
</div>
</div>
@@ -0,0 +1,271 @@
@* Instance - Repository *@
@model Model_Repository
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Repository Accounts";
Model.AdditionalFiles_JS.Add("js/sbc-res-pag.js");
Model.AdditionalFiles_JS.Add("js/sbc-ins-rep.js");
}
<div class="sbc-panel Repositories fullheight">
<script type="text/javascript">
sbc.dlg.title.createItem = '@(Localiser.GetJavascriptText("caption.repository.create"))';
sbc.dlg.title.updateItem = '@(Localiser.GetJavascriptText("caption.repository.update"))';
sbc.lbl.selectOwner = '@(Localiser.GetJavascriptText("caption.select.owner"))';
</script>
<div class="sbc-panel-data sbc-flex">
@* Filter form - wrap header to include paging controls. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRepositoryFilter", @class = "sbc-form", @onSubmit = "return false;" }))
{
<div class="sbc-panel-header">
<div class="row">
<div class="fixed">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="col center"><div class="sbc-panel-caption">@Localiser["label.repositories"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
@* Filter Panel *@
<div class="sbc-panel-filter">
<div class="row pb4">
<div class="col-sm-5 sbc-label text-left text-sm-right">@Localiser["label.search"]</div>
<div class="col-sm-7">
@Html.TextBoxFor(model => model.Filter.Search, new { @id = "", @class = "FilterSearch", @maxlength = "32" })
</div>
</div>
</div>
}
<div class="sbc-panel-item-table sbc-flex">
<table class="sbc-item-list">
<thead>
<tr>
<th class="pl-2">@Localiser["label.context"]</th>
<th class="">@Localiser["label.owner"]</th>
<th class="">@Localiser["label.server"]</th>
<th class="">@Localiser["label.instance"]</th>
<th class="">@Localiser["label.database"]</th>
<th class="center">@Localiser["label.status"]</th>
<th class="right" style="width:2%;"></th>
</tr>
<tr><td colspan="6"><div class="sbc-panel-messages"></div></td></tr>
</thead>
<tbody></tbody>
</table>
</div>
@* Element templates *@
<div class="sbc-panel-item-template">
@* Filter Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu RepositoryFilter">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
@* Repository row template - table is ignored. *@
<table class="">
<tr class="sbc-row-item Repository">
<td><div class="RepositoryContext pl-1"></div></td>
<td><div class="RepositoryOwnerName"></div></td>
<td><div class="RepositoryServerName"></div></td>
<td><div class="RepositoryInstanceName"></div></td>
<td><div class="RepositoryDatabaseName"></div></td>
<td><div class="RepositoryStatusDesc center"></div></td>
<td>
<div class="right">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</td>
</tr>
</table>
@* Item Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu Repository">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
</div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRepositoryId", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Repository.Id, new { @id = "", @class = "RepositoryId" })
}
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOwners", @onSubmit = "return false;" }))
{
}
<div id="dlgRepository" class="sbc-dialog">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRepository", @class = "sbc-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Repository.Exists, new { @id = "", @class = "RepositoryExists" })
@Html.HiddenFor(model => model.Repository.Id, new { @id = "", @class = "RepositoryId" })
@* Repository Owner *@
<div class="sbc-form-control RepositoryOwner">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for=""><span>@Localiser["label.owner"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control row">
<div class="col">
@Html.HiddenFor(model => model.Repository.OwnerId, new { @id = "", @class = "RepositoryOwnerId" })
<input type="text" class="RepositoryOwner sbc-input" readonly="readonly" />
</div>
<div class="fixed">
<div class="sbc-action RepositoryOwnerSearch"><img src="~/img/action/search24.png" alt="" title="" /></div>
</div>
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository Context *@
<div class="sbc-form-control RepositoryContext">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifRepositoryContext"><span>@Localiser["label.context"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Repository.Context, new { @id = "ifRepositoryContext", @class = "RepositoryContext sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Context) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository Server Name *@
<div class="sbc-form-control RepositoryServerName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifRepositoryServerName"><span>@Localiser["label.server"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Repository.ServerName, new { @id = "ifRepositoryServerName", @class = "RepositoryServerName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_ServerName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository Instance Name *@
<div class="sbc-form-control RepositoryInstanceName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifRepositoryInstanceName"><span>@Localiser["label.instance"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Repository.InstanceName, new { @id = "ifRepositoryInstanceName", @class = "RepositoryInstanceName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_InstanceName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository Database Name *@
<div class="sbc-form-control RepositoryDatabaseName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifRepositoryDatabaseName"><span>@Localiser["label.database"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Repository.DatabaseName, new { @id = "ifRepositoryDatabaseName", @class = "RepositoryDatabaseName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_DatabaseName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository User Name *@
<div class="sbc-form-control RepositoryUserName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifRepositoryUserName"><span>@Localiser["label.user"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Repository.UserName, new { @id = "ifRepositoryUserName", @class = "RepositoryUserName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_UserName) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository Password *@
<div class="sbc-form-control RepositoryPassword">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifRepositoryPassword"><span>@Localiser["label.password"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.PasswordFor(model => model.Repository.Password, new { @id = "ifRepositoryPassword", @class = "RepositoryPassword sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Password) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Repository Status *@
<div class="sbc-form-control RepositoryStatus">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="cbRepositoryStatus"><span>@Localiser["label.status"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
<select id="cbRepositoryStatus" class="sbc-select RepositoryStatus" name="Repository.StatusCode"></select>
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
</div>
@* Select Item dialog and script *@
<partial name="~/Views/Common/SelectItem.cshtml" />
</div>
@@ -0,0 +1,249 @@
@* Instance - Param *@
@model Model_Param
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Param Accounts";
Model.AdditionalFiles_JS.Add("js/sbc-res-pag.js");
Model.AdditionalFiles_JS.Add("js/sbc-prm.js");
}
<div class="sbc-panel Params fullheight">
<script type="text/javascript">
sbc.dlg.title.createItem = '@(Localiser.GetJavascriptText("caption.param.create"))';
sbc.dlg.title.updateItem = '@(Localiser.GetJavascriptText("caption.param.update"))';
sbc.lbl.selectItem = '@(Localiser.GetJavascriptText("caption.select.item"))';
sbc.msg.itemNotSelected = '@(Localiser.GetJavascriptText("msg.warn.item.not.selected"))';
</script>
<div class="sbc-panel-data sbc-flex">
@* Filter form - wrap header to include paging controls. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmParamFilter", @class = "sbc-form", @onSubmit = "return false;" }))
{
<div class="sbc-panel-header">
<div class="row">
<div class="fixed">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="col center"><div class="sbc-panel-caption">@Localiser["label.params"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
@* Filter Panel *@
<div class="sbc-panel-filter">
<div class="row">
<div class="col-sm-8 row pb4">
<div class="col-sm-3 sbc-label text-left text-sm-right">@Localiser["label.item"]</div>
<div class="col row">
<div class="fixed">
@Html.DropDownListFor(model => model.Filter.ItemType, Model.GetParamItemTypesForSelect(), new { @id = "", @class = "FilterItemType mr-1" })
</div>
<div class="col-sm row">
<input type="hidden" id="" name="Filter.ItemId" class="FilterItemId" />
<div class="col">
<input type="text" id="" class="FilterItem fullwidth" readonly="readonly" style="" />
</div>
<div class="fixed">
<div class="sbc-action FilterItemSearch"><img src="/img/action/search24.png" alt="" title=""></div>
</div>
</div>
</div>
</div>
<div class="col-sm-4 row pb4">
<div class="col-sm-4 sbc-label text-left text-sm-right">@Localiser["label.search"]</div>
<div class="col-sm-8">
@Html.TextBoxFor(model => model.Filter.Search, new { @id = "", @class = "FilterSearch fullwidth", @maxlength = "32" })
</div>
</div>
</div>
</div>
}
<div class="sbc-panel-item-table sbc-flex">
<table class="sbc-item-list">
<thead>
<tr>
<th class="pl-2">@Localiser["label.item"]</th>
<th class="">@Localiser["label.context"]</th>
<th class="">@Localiser["label.name"]</th>
<th class="">@Localiser["label.value"]</th>
<th class="center">@Localiser["label.status"]</th>
<th class="right" style="width:2%;"></th>
</tr>
<tr><td colspan="6"><div class="sbc-panel-messages"></div></td></tr>
</thead>
<tbody></tbody>
</table>
</div>
@* Element templates *@
<div class="sbc-panel-item-template">
@* Filter Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu ParamFilter">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
@* Param row template - table is ignored. *@
<table class="">
<tr class="sbc-row-item Param">
<td><div class="ParamItem pl-1"></div></td>
<td><div class="ParamContext"></div></td>
<td><div class="ParamName"></div></td>
<td><div class="ParamValue"></div></td>
<td><div class="ParamStatusDesc center"></div></td>
<td>
<div class="right">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sbc-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span><span class="sbc-ellipsis-dot"></span></a>
<ul class="dropdown-menu dropdown-menu-right sbc-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</td>
</tr>
</table>
@* Item Burger option template - ul is ignored *@
<ul class="sbc-dropdown-menu Param">
<li class="sbc-dropdown-item sbc-action"><div class="sbc-label">XXX</div></li>
</ul>
</div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmParamId", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Param.Id, new { @id = "", @class = "ParamId" })
}
<div id="dlgParam" class="sbc-dialog">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmParam", @class = "sbc-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Param.Exists, new { @id = "", @class = "ParamExists sbc-input" })
@Html.HiddenFor(model => model.Param.Id, new { @id = "", @class = "ParamId sbc-input" })
@* Item *@
<div class="sbc-form-control ParamItem">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifParamName"><span>@Localiser["label.item"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.HiddenFor(model => model.Param.ItemType, new { @class = "ParamItemType sbc-input" })
@Html.HiddenFor(model => model.Param.ItemId, new { @class = "ParamItemId sbc-input" })
<input type="text" id="ifParamItem" class="ParamItem sbc-input" readonly="readonly" />
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Context *@
<div class="sbc-form-control ParamContext">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifParamContext"><span>@Localiser["label.context"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Param.Context, new { @id = "ifParamContext", @class = "ParamContext sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Context) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Name *@
<div class="sbc-form-control ParamName">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifParamName"><span>@Localiser["label.name"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextBoxFor(model => model.Param.Name, new { @id = "ifParamName", @class = "ParamName sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Name) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Value *@
<div class="sbc-form-control ParamValue">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifParamValue"><span>@Localiser["label.value"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.HiddenFor(model => model.Param.ValueChanged, new { @id = "", @class = "ParamValueChanged sbc-input" })
@Html.TextBoxFor(model => model.Param.Value, new { @id = "ifParamValue", @class = "ParamValue sbc-input", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Value) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Notes *@
<div class="sbc-form-control ParamNotes">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="ifParamNotes"><span>@Localiser["label.notes"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
@Html.TextAreaFor(model => model.Param.Notes, new { @id = "ifParamNotes", @class = "ParamNotes sbc-textarea", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Notes) })
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
@* Status *@
<div class="sbc-form-control ParamStatus">
<div class="row">
<div class="col-sm-3">
<div class="sbc-label">
<label for="cbParamStatus"><span>@Localiser["label.status"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sbc-control">
<select id="cbParamStatus" class="sbc-select ParamStatus" name="Param.StatusCode"></select>
</div>
<div class="sbc-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
</div>
@* Select Item dialog and script *@
<partial name="~/Views/Common/SelectItem.cshtml" />
</div>
@@ -0,0 +1,66 @@
@model Model_Security
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Change Password";
}
<div class="">
<div class="sbc-panel-form security">
<div class="sbc-header"><div class="sbc-caption">@Localiser["security.caption.change.password"]</div></div>
<div class="sbc-content">
<div class="spacer4"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmChangePassword", @class = "sbc-form", @onsubmit = "return false;" }))
{
@if (!Model.RequestPasswordMode)
{
/* Current password - required when setting password within user session. */
<div class="sbc-form-control PasswordCurrent">
<div class="sbc-label"><label for="ifPasswordCurrent"><span><span class="required">*</span> @Localiser["label.password.current"]</span></label></div>
<div class="sbc-control">
@Html.PasswordFor(model => model.PasswordCurrent, new { @id = "ifPasswordCurrent", @class = "sbc-input", @maxlength = Model.PasswordMaxLength })
</div>
<div class="sbc-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="sbc-form-control Password">
<div class="sbc-label"><label for="ifPassword"><span><span class="required">*</span> @Localiser["label.password"]</span></label></div>
<div class="sbc-control">
@Html.PasswordFor(model => model.Password, new { @id = "ifPassword", @class = "sbc-input", @maxlength = Model.PasswordMaxLength })
</div>
<div class="row">
<div class="col-auto sbc-label-text pt4">@Localiser["label.strength"]&nbsp;</div>
<div id="" class="col-auto"><div id="prgPasswordStrength" class="col-auto"></div></div>
</div>
<div class="sbc-message"></div>
</div>
<div class="sbc-form-control PasswordConfirm">
<div class="sbc-label"><label for="ifPasswordConfirm"><span><span class="required">*</span> @Localiser["label.password.confirm"]</span></label></div>
<div class="sbc-control">
@Html.PasswordFor(model => model.PasswordConfirm, new { @id = "ifPasswordConfirm", @class = "sbc-input", @maxlength = Model.PasswordMaxLength })
</div>
<div class="sbc-message"></div>
</div>
<div class="spacer4"></div>
<div class="">
<div id="pbChangePassword" class="sbc-button-action fullwidth pointer" style="margin:16px 0;">@Localiser["security.caption.change.password"]</div>
</div>
}
</div>
</div>
</div>
@@ -0,0 +1,37 @@
@model Model_Security
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Request Password";
}
<div class="">
<div class="sbc-panel-form security">
<div class="sbc-header"><div class="sbc-caption">@Localiser["caption.request.password"]</div></div>
<div class="sbc-content">
<div class="spacer4"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmRequestPassword", @class = "sbc-form", @onsubmit = "return false;" }))
{
<div class="sbc-form-control EmailAddress">
<div class="sbc-label">
<label for="ifEmailAddress"><span><span class="required">*</span> @Localiser["label.email.address"]</span></label>
</div>
<div class="sbc-control">
@Html.TextBoxFor(model => model.EmailAddress, new { @id = "ifEmailAddress", @class = "sbc-input lower", @maxlength = "128" })
</div>
<div class="sbc-message"></div>
</div>
<div class="spacer4"></div>
<div class="">
<div id="pbRequestPassword" class="sbc-button-action fullwidth pointer" style="margin:16px 0;">@Localiser["caption.request.password"]</div>
</div>
}
</div>
</div>
</div>
@@ -0,0 +1,62 @@
@model Model_Security
@inject SbmLocaliser Localiser
@{
ViewData["Title"] = "Signin";
}
<div class="">
<div class="sbc-panel-form security">
<div class="sbc-header"><div class="sbc-caption">@Localiser["label.signin"]</div></div>
<div class="sbc-content">
<div class="spacer4"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSignIn", @class = "sbc-form", @onsubmit = "return false;" }))
{
<div class="sbc-form-control EmailAddress">
<div class="sbc-label">
<label for="ifEmailAddress"><span><span class="required">*</span> @Localiser["label.email.address"]</span></label>
</div>
<div class="sbc-control">
@Html.TextBoxFor(model => model.EmailAddress, new { @id = "ifEmailAddress", @class = "sbc-input lower", @maxlength = @Model.GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sbc-message"></div>
</div>
<div class="spacer2"></div>
<div class="sbc-form-control Password">
<div class="sbc-label">
<label for="ifPassword"><span><span class="required">*</span> @Localiser["label.password"]</span></label>
</div>
<div class="sbc-control">
@Html.PasswordFor(model => model.Password, new { @id = "ifPassword", @class = "sbc-input", @maxlength = @Model.PasswordMaxLength })
</div>
<div class="sbc-message"></div>
</div>
<div class="spacer2"></div>
<div class="sbc-form-control">
<div class="row">
<div class="col-6">
@Html.CheckBoxFor(model => model.KeepMeSignedIn, new { @id = "chkKeepMeSignedIn" })
&nbsp;
<span class="sbc-label pointer" onclick="$('#chkKeepMeSignedIn').click()">@Localiser["label.keep.signed.in"]</span>
</div>
<div class="col-6 right">
<div class="sbc-action-link" style="font-size:12px;" onclick="sbc.goToPage('Security/RequestPassword');">@Localiser["label.forgotten.password"]</div>
</div>
</div>
</div>
<div class="spacer2"></div>
<div class="">
<div id="pbSignIn" class="sbc-button-action fullwidth pointer" style="margin:16px 0;">@Localiser["label.signin"]</div>
</div>
}
</div>
</div>
</div>
@@ -5,49 +5,213 @@
Application: BusinessMgr Web Control
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>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - SaBusinessMgrCoreWebAdmin</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
<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"] - BusMgr Control</title>
@* Stylesheets *@
<link rel="stylesheet" href="~/lib/jqueryui/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/sbc-base.css?@Model.HrefVersion" />
<link rel="stylesheet" href="~/css/sbc-main.css?@Model.HrefVersion" />
<link rel="stylesheet" href="~/css/sbc-data.css?@Model.HrefVersion" />
<link rel="stylesheet" href="~/css/sbc-form.css?@Model.HrefVersion" />
@foreach (var cssFile in Model.AdditionalFiles_CSS)
{
<link rel="stylesheet" href="~/@cssFile?@Model.HrefVersion" />
}
@* Scripts *@
<script>
var sbc = sbc || { page: {}, mdl: {}, fn: {}, url: {}, links: {}, form: {}, data: {}, ajax: {}, msg: {}, lbl: {}, dlg: { title: {}, btn: {} }, $frm: {}, $dlg: {}, $pnl: {}, session: {}, kli: {} };
sbc.url.base = '@Model.RedirectLocation.Base';
sbc.url.home = '@Model.RedirectLocation.Home';
sbc.url.sessionExpired = '@Model.RedirectLocation.Home_SessionExpired';
sbc.links.facebook = '@Model.Links.Facebook';
sbc.links.linkedin = '@Model.Links.LinkedIn';
sbc.dlg.title.alert = '@Localiser.GetJavascriptText("dialog.title.alert")';
sbc.dlg.title.confirm = '@Localiser.GetJavascriptText("dialog.title.confirm")';
sbc.dlg.title.progress = '@Localiser.GetJavascriptText("dialog.title.progress")';
sbc.dlg.title.signOut = '@Localiser.GetJavascriptText("dialog.title.signout")';
sbc.lbl.yes = '@Localiser["label.yes"]';
sbc.lbl.no = '@Localiser["label.no"]';
sbc.lbl.ok = '@Localiser["label.ok"]';
sbc.lbl.save = '@Localiser["label.save"]';
sbc.lbl.close = '@Localiser["label.close"]';
sbc.lbl.cancel = '@Localiser["label.cancel"]';
sbc.lbl.refresh = '@(Localiser.GetJavascriptText("label.refresh"))';
sbc.lbl.create = '@(Localiser.GetJavascriptText("label.create"))';
sbc.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
sbc.lbl.emails = '@(Localiser.GetJavascriptText("label.emails"))';
sbc.lbl.charAvailable = '@Localiser.GetJavascriptText("info.char.available")';
sbc.lbl.charsAvailable = '@Localiser.GetJavascriptText("info.chars.available", "{0}")';
sbc.lbl.noneSelected = '@Localiser.GetJavascriptText("label.selected.none")';
sbc.lbl.numberSelected = '@Localiser.GetJavascriptText("label.selected.count", "{0}")';
sbc.msg.signOutConfirm = '@Localiser.GetJavascriptText("security.msg.signout.confirm")';
sbc.msg.somethingGoneWrong = '@Localiser.GetJavascriptText("msg.something.gone.wrong")';
sbc.msg.dataRefreshing = '@Localiser.GetJavascriptText("msg.data.refreshing")';
sbc.msg.dataQuerying = '@Localiser.GetJavascriptText("msg.data.querying")';
sbc.msg.dataNoneFound = '@Localiser.GetJavascriptText("msg.data.noneFound")';
sbc.session.cookieName = '@Model.SessionCookieName';
sbc.awaitingLogin = @awaitingLogin;
sbc.kli.active = @keepLoggedIn;
sbc.kli.interval = @(Sbm.Config.Control.Session.Cookie.KeepLoggedInTimeoutMillis);
sbc.debugMode = @debug;
</script>
</head>
<body>
<header>
<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">SaBusinessMgr-Control</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">Home</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
@(Model.CopyrightNotice) - Sarand BusinessMgr
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
@* Header *@
<header id="sbc-page-header">
@* Navigation bar *@
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">SBM Control</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">
@if (Model.AwaitingLogin)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Policies" asp-action="Privacy">@Localiser["label.privacy"]</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Security" asp-action="">@Localiser["label.signin"]</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">@Localiser["label.home"]</a>
</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 sbc-action" onclick="sbc.goToPage('Account/UserAccount');">@Localiser["label.users"]</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.instances"]
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sbc-action" onclick="sbc.goToPage('Instance/Owner');">@Localiser["label.owners"]</div>
<div class="dropdown-item text-dark sbc-action" onclick="sbc.goToPage('Instance/Repository');">@Localiser["label.repositories"]</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.configuration"]
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sbc-action" onclick="sbc.goToPage('Param/Param');">@Localiser["label.params"]</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.system"]
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item text-dark sbc-action" onclick="sbc.goToPage('System/Database');">@Localiser["label.database"]</div>
<div class="dropdown-item text-dark sbc-action" onclick="sbc.goToPage('System/Logs');">@Localiser["label.logs"]</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 sbc-action" onclick="sbc.navigateTo('Security/ChangePassword');">@Localiser["caption.change.password"]</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item text-dark sbc-action" onclick="sbc.onSignOut();">@Localiser["label.signout"]</div>
</div>
</li>
}
</ul>
</div>
</div>
</nav>
</header>
@* Page body *@
<div id="sbc-page-body" class="container">
<main role="main" class="pb-3 fullheight">
@RenderBody()
</main>
</div>
@* Footer *@
<footer id="sbc-page-footer" class="border-top footer text-muted">
<div class="container">
@(Model.CopyrightNotice) - Sarand BusinessMgr
</div>
</footer>
@* Form to allow page navigation. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmNavigate" })) { }
<div id='sbc-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/jqueryui/jquery-ui.js"></script>
<script src="~/js/sbc-main.js?@Model.HrefVersion"></script>
@foreach (var jsFile in Model.AdditionalFiles_JS)
{
<script src="~/@jsFile?@Model.HrefVersion"></script>
}
@RenderSection("Scripts", required: false)
</body>
</html>
@@ -1,4 +1,8 @@
@using Sarand.BusinessMgr.Net.Core.Web.App
@using Sarand.BusinessMgr.Net.Core.Web.Control
@using Sarand.BusinessMgr.Net.Core.Web.Control.Models
@using Sarand.BusinessMgr.Net.Standard.Base.App
@using Sarand.BusinessMgr.Net.Core.Web.App
@using Sarand.BusinessMgr.Web.Control
@using Sarand.BusinessMgr.Web.Control.Models
@using Sarand.BusinessMgr.Web.Control.Models.Account
@using Sarand.BusinessMgr.Web.Control.Models.Instance
@using Sarand.BusinessMgr.Web.Control.Models.Param
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -4,7 +4,7 @@
"Environments": [ "Development" ],
"Instance": "Dev",
"ControlDatabase": {
"Location": "MsSql:Roman\\SQL2016.SaBusinessMgr_Control",
"Location": "MsSql:.\\SQL2016.SaBusinessMgr_Control",
"User": "sapphire",
"Password": "sarand"
},
@@ -0,0 +1,446 @@
/*
BusMgr Control - 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;
}
table.sbmc-striped
{
border-spacing: 0 2px;
}
table.sbmc-striped tr.sbmc-odd
{
background-color:#fff;
}
table.sbmc-striped tr.sbmc-even
{
background-color:#f8f8f8;
}
th, td
{
vertical-align: top;
padding: 0;
}
label
{
margin: 0;
}
input
{
margin: 0;
padding: 0 2px 1px 2px;
}
textarea
{
resize: none;
}
select
{
margin: 0;
padding: 3px 2px 1px 2px;
}
.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;
}
.left
{
text-align: left !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%;
}
.fullheight
{
height: 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,491 @@
/*
Control - Data stylesheet.
*/
/* Data panel */
.sbc-panel-data
{
margin: 6px auto;
background-color: #f8f8f8;
border: solid 1px var(--color-panel-data-border);
border-radius: 6px;
overflow: hidden;
}
.sbc-layout-flex .sbc-panel-data.sbc-flex
{
height: 100%;
display: flex;
flex-direction: column;
}
/* Data panel - Header */
.sbc-panel-data .sbc-panel-header
{
padding: 1px 8px 2px 8px;
min-height: 24px;
color: #f0f0f0;
background-color: var(--color-panel-data-background);
border-bottom: solid 1px var(--color-panel-data-border);
}
.sbc-panel-data .sbc-panel-header .sbc-panel-caption
{
margin-top: 4px;
font-size: 1.2rem;
font-weight: bold;
}
/* Data panel - Actions */
.sbc-panel-data ul.nav.sbc-burger
{
margin-top: 2px;
}
.sbc-panel-data .sbc-panel-header .sbc-action
{
margin: 2px 0 0 8px;
border-radius: 4px;
}
.sbc-panel-data .sbc-panel-header .sbc-action:hover
{
background-color: #b0b0b0;
box-shadow: 0 0 4px 2px #80808080;
}
/* Data panel - Filter */
.sbc-panel-data .sbc-panel-filter
{
padding: 6px 8px;
border-bottom: solid 1px var(--color-panel-data-border);
}
.sbc-panel-data .sbc-panel-filter .sbc-label
{
padding: 2px 8px 0 0;
}
/* Data panel - Content */
.sbc-panel-data .sbc-panel-content
{
margin: 4px 4px;
padding: 4px 4px;
background-color: #f8f8f8;
}
.sbc-panel-data .sbc-panel-content .sbc-label
{
padding-top: 6px;
padding-right: 12px;
text-align: right;
}
.sbc-panel-data .sbc-panel-content .sbc-value
{
padding-top: 2px;
}
.sbc-panel-data .sbc-panel-content input[type="text"],
.sbc-panel-data .sbc-panel-content select
{
min-width: 75%;
max-width: 90%;
width: 90%;
}
.sbc-panel-data .sbc-panel-content .sbc-button
{
min-width: auto;
margin-left: 4px;
padding: 4px 6px;
}
/* Paging */
.sbc-results-paging
{
margin-right: 12px;
}
.sbc-results-paging .sbc-paging-info
{
font-size: .8rem;
}
.sbc-panel-data .sbc-panel-header .sbc-results-paging .sbc-action
{
margin: 2px 1px 0 1px;
background-color: #c0c0c0;
box-shadow: 0 0 0 1px #40404080 inset;
}
.sbc-panel-data .sbc-panel-header .sbc-results-paging .sbc-action:hover
{
background-color: #e0e0e0;
}
/* Panel header - Burger */
ul.nav.sbc-burger
{
margin: 0;
}
ul.nav.sbc-burger .dropdown
{
margin: 2px 0 2px 0;
padding: 2px 2px;
border: 1px solid transparent;
border-radius: 2px;
}
ul.nav.sbc-burger .dropdown:hover
{
opacity: .75;
}
ul.nav.sbc-burger.navbar-light .dropdown:hover
{
background-color: #f8f8f8;
border-color: #606060;
}
ul.nav.sbc-burger.navbar-dark .dropdown:hover
{
border-color: #c0c0c0;
}
ul.nav.sbc-burger .sbc-ellipsis-dot
{
display: inline-block;
width: 4px;
height: 4px;
margin: 0 1px 3px 1px;
background-color: #808080;
border-radius: 2px;
}
ul.nav.sbc-burger ul.sbc-dropdown-menu
{
padding: 4px 8px 4px 4px;
border-radius: 2px 2px 4px 8px;
box-shadow: 0 0 8px 2px #80808080;
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item
{
margin: 2px 4px 2px 0;
padding: 2px 4px 2px 30px;
background-repeat: no-repeat;
background-position: left center;
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-refresh
{
background-image: url('../img/action/refresh24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-info
{
background-image: url('../img/action/info24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-add
{
background-image: url('../img/action/add24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-edit
{
background-image: url('../img/action/edit24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-remove
{
background-image: url('../img/action/delete24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-experience
{
background-image: url('../img/action/trophy24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-user
{
background-image: url('../img/action/user24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-users
{
background-image: url('../img/action/users24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-mail
{
background-image: url('../img/action/mail24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-location
{
background-image: url('../img/action/location-24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-image
{
background-image: url('../img/action/image-24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-process
{
background-image: url('../img/action/process24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-upload
{
background-image: url('../img/action/upload24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-download
{
background-image: url('../img/action/download24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-network
{
background-image: url('../img/action/network-24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-close
{
background-image: url('../img/action/prev24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-itemlock
{
background-image: url('../img/action/lock24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item.sbc-unlock
{
background-image: url('../img/action/unlock24.png');
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item:hover
{
background-color: #f8f8f8;
box-shadow: none;
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item .sbc-label
{
margin: 0;
padding: 0 0 0 4px;
}
ul.nav.sbc-burger ul.sbc-dropdown-menu li.sbc-action.sbc-dropdown-item:hover .sbc-label
{
background-color: #f8f8f8;
box-shadow: 0 0 2px 1px #80808080;
}
/* Data Items panel */
.sbc-panel-results
{
}
@media (min-width: 576px)
{
.sbc-panel-results
{
}
}
/* Messages */
.sbc-panel-messages
{
margin: 8px 16px;
padding: 2px 12px 2px 8px;
font-size: 16px;
}
.sbc-panel-messages .sbc-message
{
margin: 4px 0;
padding-left: 24px;
background-position: left top;
background-repeat: no-repeat;
}
.sbc-panel-messages .sbc-message.sbc-error
{
color: #e3120b;
background-image: url("../img/icon/error-18.png");
}
/* Item List table */
.sbc-panel-item-table
{
width: 100%;
min-height: 240px;
background-color: white;
overflow-y: auto;
}
.sbc-layout-flow .sbc-panel-item-table
{
}
.sbc-layout-flex .sbc-panel-item-table.sbc-flex
{
flex: 1;
min-height: 0;
}
table.sbc-item-list
{
width: 100%;
border-spacing: 0;
border-collapse: separate;
overflow: auto;
}
table.sbc-item-list thead tr
{
}
table.sbc-item-list thead th
{
position: sticky;
top: 0;
padding: 4px 2px 2px 2px;
z-index: 1;
background-color: #f8f8f8;
border-bottom: 1px solid #d0d0d0;
}
table.sbc-item-list tbody th
{
position: sticky;
left: 0;
background-color: #f8f8f8;
}
table.sbc-item-list tbody tr:hover
{
background-color: lightyellow;
}
table.sbc-item-list tbody tr.sbc-sel
{
background-color: palegreen;
}
table.sbc-item-list tbody td
{
padding: 0 2px;
}
/* Item */
.sbc-panel-item-template
{
display: none;
}
.sbc-panel-item
{
margin: 6px auto;
border: solid 1px #979797;
border-radius: 6px;
overflow: hidden;
}
.sbc-panel-item > .sbc-panel-header
{
padding: 2px 8px;
min-height: 24px;
background-color: #e0e0e0;
border-bottom: solid 1px #979797;
}
.sbc-panel-item > .sbc-panel-header .sbc-panel-caption
{
margin-top: 4px;
font-size: 1.2rem;
font-weight: bold;
}
.sbc-panel-item > ul.nav.sbc-burger
{
margin: 0;
}
.sbc-panel-item > .sbc-panel-header .sbc-action
{
border-radius: 4px;
}
.sbc-panel-item > .sbc-panel-header .sbc-panel-caption .sbc-action:hover
{
color: var(--color-light);
}
.sbc-panel-item > .sbc-panel-header .sbc-actions .sbc-action
{
margin: 2px 0 0 8px;
}
.sbc-panel-item > .sbc-panel-header .sbc-actions .sbc-action:hover
{
background-color: #c0c0c0;
box-shadow: 0 0 4px 2px #c0c0c080;
}
.sbc-panel-item > .sbc-panel-content
{
margin: 4px 8px 2px 8px;
padding: 2px 2px;
}
.sbc-panel-item .sbc-note
{
font-size: 0.8rem;
}
/* Item statuses */
.sbc-status-awa
{
color: darkorange;
}
.sbc-status-atv, .sbc-status-cur
{
color: green;
}
.sbc-status-ina
{
color: darkred;
}
.sbc-status-hld
{
color: dodgerblue;
}
.sbc-status-del
{
color: purple;
}
/* The End */
@@ -0,0 +1,281 @@
/*
BusMgr Control - Form stylesheet.
*/
/* Panels */
.sbc-panel-info, .sbc-panel-form
{
max-width: 800px;
margin: 16px auto;
padding: 0;
background-color: #fff;
border: solid 1px #979797;
border-color: var(--color-primary);
border-radius: 4px;
overflow: hidden;
}
.sbc-panel-info .sbc-panel-info
{
margin: 6px auto;
padding: 0;
background-color: #fff;
border: solid 1px #979797;
border-color: var(--color-primary);
border-radius: 4px;
overflow: hidden;
}
.sbc-panel-form.security
{
max-width: 520px;
}
@media (min-width: 576px)
{
.sbc-panel-info, .sbc-panel-form
{
}
}
.sbc-panel-info > .sbc-header,
.sbc-panel-form > .sbc-header
{
padding: 8px 12px;
text-align: center;
background-color: #e8e8e8;
border-bottom: solid 1px #606060;
border-color: var(--color-primary);
}
.sbc-panel-info .sbc-panel-info > .sbc-header
{
padding: 6px 4px;
}
.sbc-panel-info > .sbc-header .sbc-caption,
.sbc-panel-form > .sbc-header .sbc-caption
{
font-size: 1.5rem;
font-weight: bold;
color: var(--color-primary-dark);
}
.sbc-panel-info .sbc-panel-info > .sbc-header .sbc-caption
{
font-size: 1.25rem;
}
.sbc-panel-info > .sbc-header .sbc-action img:hover,
.sbc-panel-form > .sbc-header .sbc-action img:hover
{
background-color: #f0f0f0;
border-radius: 4px;
box-shadow: 2px 2px 4px 0 #80808080;
}
.sbc-panel-info > .sbc-content,
.sbc-panel-form > .sbc-content
{
max-height: 400px;
padding: 4px 8px;
background-color: #f8f8f8;
overflow-y: auto;
}
.sbc-panel-info > .sbc-content
{
padding: 8px 12px;
}
.sbc-panel-info .sbc-panel-info > .sbc-content
{
padding: 4px 8px;
}
.sbc-panel-section.sbc-edit .sbc-panel-info,
.sbc-panel-section.sbc-populating .sbc-button-action.edit,
.sbc-panel-section .sbc-panel-form
{
display: none;
}
.sbc-panel-section.sbc-edit .sbc-panel-form
{
display: block;
}
/* Form */
.sbc-form
{
}
/* Labels, values, notes */
.sbc-panel-info .sbc-label, .sbc-panel-info .sbc-value
{
margin-bottom: 6px;
font-size: 1rem;
}
.sbc-panel-info .sbc-label
{
padding-right: 12px;
font-weight: bold;
color: #808080;
}
.sbc-panel-info .sbc-value
{
}
/* Controls */
.sbc-form .sbc-form-control
{
margin: 0 0 8px 0;
}
.sbc-form .sbc-form-control .sbc-label
{
margin: 4px 0 4px 0;
font-size: 1rem;
line-height: 1.25;
}
.sbc-form .sbc-form-control .sbc-label-text
{
font-size: 1rem;
line-height: 1.25;
}
.sbc-form .sbc-form-control .sbc-note
{
font-size: 0.9rem;
line-height: 1.2;
}
.sbc-form .sbc-input, .sbc-form .sbc-textarea, .sbc-form .sbc-select
{
padding: 3px 8px 4px 8px;
width: 100%;
font-size: 1rem;
line-height: 1.25;
background-color: #fff;
border: 1px solid #595959;
border-radius: 2px;
outline: none;
}
.sbc-form .sbc-input.sbc-calendar
{
max-width: 180px;
}
.sbc-form .sbc-textarea, .sbc-form .sbc-select[multiple="multiple"]
{
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
overflow-y: scroll;
}
.sbc-form .sbc-input:read-only, .sbc-form .sbc-textarea:read-only, .sbc-form .sbc-select:disabled
{
color: #404040;
opacity: 1;
background-color: #f0f0f0;
}
.sbc-form .sbc-form-control.sbc-error .sbc-input, .sbc-form .sbc-form-control.sbc-error .sbc-textarea, .sbc-form .sbc-form-control.sbc-error .sbc-select
{
color: #606060; /*#DC143C;*/
border-color: #606060; /*#DC143C;*/
}
/* Control messages */
.sbc-form .sbc-form-control .sbc-message
{
display: none;
margin-top: 4px;
padding-left: 24px;
font-size: 1.00rem;
background-position: left top;
background-repeat: no-repeat;
}
.sbc-form .sbc-form-control.sbc-error .sbc-message
{
min-height: 18px;
display: block;
color: #e3120b;
background-image: url("../img/icon/error-18.png");
}
/* Buttons */
.sbc-form button.sbc-button,
.sbc-form input[type="button"].sbc-button,
.sbc-form input[type="submit"].sbc-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;
}
.sbc-form button.sbc-button:hover,
.sbc-form input[type="button"].sbc-button:hover,
.sbc-form input[type="submit"].sbc-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,437 @@
/*
BusMgr Control - Main stylesheet.
*/
/* Global variables */
:root
{
--color-OliveGreen: #808000;
--color-Purple: #76448a;
/* Colour analysis: https://www.colorhexa.com/ */
/* #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;
/* Data panels */
--color-panel-data: var(--color-Purple);
--color-panel-data-background: var(--color-Purple);
--color-panel-data-border: var(--color-Purple);
}
/* Document */
html
{
font-size: 14px;
}
@media (min-width: 768px)
{
html
{
font-size: 14px;
}
}
body
{
font-family: Arial, Verdana, Helvetica, Lucida, sans-serif;
}
@media screen and (max-height: 399px)
{
#sbc-page-body
{
min-height: 240px;
}
}
@media screen and (min-height: 400px)
{
html
{
height: 100vh;
}
body.sbc-layout-flex
{
height: 100vh;
display: flex;
flex-direction: column;
}
body.sbc-layout-flex #sbc-page-body
{
flex: 1;
min-height: 0;
}
}
/* Responsive */
body > .container, header > .container, footer > .container
{
padding: 0 0.5%;
}
@media (min-width: 992px)
{
.container
{
max-width: 1200px;
}
}
@media (min-width: 768px)
{
.container
{
max-width: 1200px;
}
}
@media (min-width: 576px)
{
.container
{
max-width: 1200px;
}
}
/* Header */
#sbc-page-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;
}
/* Information */
.sbc-para
{
margin: 8px 0;
}
/* Footer */
#sbc-page-footer
{
padding: 4px 0;
}
#sbc-page-footer, #sbc-page-footer div
{
font-size: .8rem;
}
/* Page caption */
.sbc-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;
}
.sbc-action, .sbc-action-text
{
cursor: pointer;
}
.sbc-action-text:hover
{
text-decoration: underline;
}
.sbc-action-link
{
display: inline-block;
font-size: 1rem;
font-weight: bold;
color: #000;
cursor: pointer;
}
.sbc-action-link:hover
{
opacity: .7;
transition: all .4s ease-in-out;
}
.sbc-link-text
{
font-size: 18px;
font-weight: bold;
}
/* Buttons */
.sbc-button-cta, .sbc-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;
}
.sbc-button-action
{
}
.sbc-button-action:hover
{
background-color: #b0b0b0;
}
.sbc-button-action:focus
{
outline: none;
box-shadow: 0 0 4px 2px #808080;
}
.sbc-button-cta:hover
{
background: #808080 !important;
}
/* --- Notification --- */
#sbc-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;
}
#sbc-notification .content
{
background-color: #fbfbfb;
overflow: hidden;
}
#sbc-notification .heading
{
font-size: 16px;
font-weight: bold;
background-color: #f0f0f0;
border-bottom: 1px solid #808080;
}
#sbc-notification .heading:after
{
content: "";
display: table;
clear: both;
}
#sbc-notification .title
{
float: left;
padding: 4px 8px;
white-space: nowrap;
}
#sbc-notification .close
{
float: right;
padding: 4px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
}
#sbc-notification .message
{
clear: both;
padding: 8px;
font-size: 14px;
}
/* --- Modal Please wait --- */
#sbc-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;
}
/* Drop down menus */
.dropdown-toggle.caret-off::before,
.dropdown-toggle.caret-off::after
{
display: none !important;
}
/* Dialogs */
.sbc-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: 1px;
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;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.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
{
padding: 4px 8px;
border-color: #d4af37;
}
.ui-dialog .ui-dialog-buttonpane button
{
min-width: 80px;
margin: 4px 8px 4px 0;
color: #fff;
background-color: #d4af37;
border-color: #d4af37;
outline: none;
}
.ui-dialog .ui-dialog-buttonpane button:disabled
{
color: #e0e0e0;
background-color: #d5be70;
border-color: #d5be70;
}
.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,61 @@
/*
Item Selection stylesheet.
*/
/* Panels */
#dlgSelectItem .SelectedItemName
{
min-height: 1.5rem;
padding: 2px 4px;
background-color: #f0f0f0;
border: 1px solid #b0b0b0;
border-radius: 4px;
overflow: hidden;
}
#dlgSelectItem .ItemFilter .sbc-label
{
margin-right: 6px;
}
#dlgSelectItem .sbc-button
{
padding: 2px 8px;
border-radius: 6px;
}
#dlgSelectItem .sbc-selectItem-items
{
margin-bottom: 8px;
padding: 2px 0;
background-color: #f0f0f0;
border: 1px solid #b0b0b0;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
overflow-x: hidden;
overflow-y: scroll;
}
#dlgSelectItem.sbc-form button.sbc-button
{
margin: 4px 0;
}
#dlgSelectItem .sbc-panel-item.Item
{
margin: 2px 4px;
padding: 2px 4px;
background-color: #fff;
border: 2px solid #d8d8ff;
border-radius: 4px;
cursor: pointer;
}
#dlgSelectItem .sbc-panel-item.Item.sel
{
background-color: #ececff;
border-color: #8a8aff;
}
/* The End */
@@ -1,71 +0,0 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.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;
}
/* 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%;
white-space: nowrap;
line-height: 60px; /* Vertically center the text there */
}
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-indent" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M3 8a.5.5 0 0 1 .5-.5h6.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H3.5A.5.5 0 0 1 3 8Z"/>
<path fill-rule="evenodd" d="M12.5 4a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 401 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ol" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5z"/>
<path d="M1.713 11.865v-.474H2c.217 0 .363-.137.363-.317 0-.185-.158-.31-.361-.31-.223 0-.367.152-.373.31h-.59c.016-.467.373-.787.986-.787.588-.002.954.291.957.703a.595.595 0 0 1-.492.594v.033a.615.615 0 0 1 .569.631c.003.533-.502.8-1.051.8-.656 0-1-.37-1.008-.794h.582c.008.178.186.306.422.309.254 0 .424-.145.422-.35-.002-.195-.155-.348-.414-.348h-.3zm-.004-4.699h-.604v-.035c0-.408.295-.844.958-.844.583 0 .96.326.96.756 0 .389-.257.617-.476.848l-.537.572v.03h1.054V9H1.143v-.395l.957-.99c.138-.142.293-.304.293-.508 0-.18-.147-.32-.342-.32a.33.33 0 0 0-.342.338v.041zM2.564 5h-.635V2.924h-.031l-.598.42v-.567l.629-.443h.635V5z"/>
</svg>

After

Width:  |  Height:  |  Size: 983 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ul" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 447 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-indent-left" viewBox="0 0 16 16">
<path d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm.646 2.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L4.293 8 2.646 6.354a.5.5 0 0 1 0-.708zM7 6.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm-5 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 517 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-indent-right" viewBox="0 0 16 16">
<path d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm10.646 2.146a.5.5 0 0 1 .708.708L11.707 8l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708l2-2zM2 6.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 503 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-bold" viewBox="0 0 16 16">
<path d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13H8.21zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"/>
</svg>

After

Width:  |  Height:  |  Size: 470 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h1" viewBox="0 0 16 16">
<path d="M8.637 13V3.669H7.379V7.62H2.758V3.67H1.5V13h1.258V8.728h4.62V13h1.259zm5.329 0V3.669h-1.244L10.5 5.316v1.265l2.16-1.565h.062V13h1.244z"/>
</svg>

After

Width:  |  Height:  |  Size: 282 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h2" viewBox="0 0 16 16">
<path d="M7.638 13V3.669H6.38V7.62H1.759V3.67H.5V13h1.258V8.728h4.62V13h1.259zm3.022-6.733v-.048c0-.889.63-1.668 1.716-1.668.957 0 1.675.608 1.675 1.572 0 .855-.554 1.504-1.067 2.085l-3.513 3.999V13H15.5v-1.094h-4.245v-.075l2.481-2.844c.875-.998 1.586-1.784 1.586-2.953 0-1.463-1.155-2.556-2.919-2.556-1.941 0-2.966 1.326-2.966 2.74v.049h1.223z"/>
</svg>

After

Width:  |  Height:  |  Size: 482 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h3" viewBox="0 0 16 16">
<path d="M7.637 13V3.669H6.379V7.62H1.758V3.67H.5V13h1.258V8.728h4.62V13h1.259zm3.625-4.272h1.018c1.142 0 1.935.67 1.949 1.674.013 1.005-.78 1.737-2.01 1.73-1.08-.007-1.853-.588-1.935-1.32H9.108c.069 1.327 1.224 2.386 3.083 2.386 1.935 0 3.343-1.155 3.309-2.789-.027-1.51-1.251-2.16-2.037-2.249v-.068c.704-.123 1.764-.91 1.723-2.229-.035-1.353-1.176-2.4-2.954-2.385-1.873.006-2.857 1.162-2.898 2.358h1.196c.062-.69.711-1.299 1.696-1.299.998 0 1.695.622 1.695 1.525.007.922-.718 1.592-1.695 1.592h-.964v1.074z"/>
</svg>

After

Width:  |  Height:  |  Size: 646 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-italic" viewBox="0 0 16 16">
<path d="M7.991 11.674 9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-strikethrough" viewBox="0 0 16 16">
<path d="M6.333 5.686c0 .31.083.581.27.814H5.166a2.776 2.776 0 0 1-.099-.76c0-1.627 1.436-2.768 3.48-2.768 1.969 0 3.39 1.175 3.445 2.85h-1.23c-.11-1.08-.964-1.743-2.25-1.743-1.23 0-2.18.602-2.18 1.607zm2.194 7.478c-2.153 0-3.589-1.107-3.705-2.81h1.23c.144 1.06 1.129 1.703 2.544 1.703 1.34 0 2.31-.705 2.31-1.675 0-.827-.547-1.374-1.914-1.675L8.046 8.5H1v-1h14v1h-3.504c.468.437.675.994.675 1.697 0 1.826-1.436 2.967-3.644 2.967z"/>
</svg>

After

Width:  |  Height:  |  Size: 579 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-underline" viewBox="0 0 16 16">
<path d="M5.313 3.136h-1.23V9.54c0 2.105 1.47 3.623 3.917 3.623s3.917-1.518 3.917-3.623V3.136h-1.23v6.323c0 1.49-.978 2.57-2.687 2.57-1.709 0-2.687-1.08-2.687-2.57V3.136zM12.5 15h-9v-1h9v1z"/>
</svg>

After

Width:  |  Height:  |  Size: 334 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type" viewBox="0 0 16 16">
<path d="m2.244 13.081.943-2.803H6.66l.944 2.803H8.86L5.54 3.75H4.322L1 13.081h1.244zm2.7-7.923L6.34 9.314H3.51l1.4-4.156h.034zm9.146 7.027h.035v.896h1.128V8.125c0-1.51-1.114-2.345-2.646-2.345-1.736 0-2.59.916-2.666 2.174h1.108c.068-.718.595-1.19 1.517-1.19.971 0 1.518.52 1.518 1.464v.731H12.19c-1.647.007-2.522.8-2.522 2.058 0 1.319.957 2.18 2.345 2.18 1.06 0 1.716-.43 2.078-1.011zm-1.763.035c-.752 0-1.456-.397-1.456-1.244 0-.65.424-1.115 1.408-1.115h1.805v.834c0 .896-.752 1.525-1.757 1.525z"/>
</svg>

After

Width:  |  Height:  |  Size: 631 B

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-unindent" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M13 8a.5.5 0 0 0-.5-.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H12.5A.5.5 0 0 0 13 8Z"/>
<path fill-rule="evenodd" d="M3.5 4a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 1 0v-7a.5.5 0 0 0-.5-.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

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