Portal signin via Control Instance
@@ -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 - < min length - 2 [red]</para>
|
||||
/// <para>1 - < min length and only 1 character type [red]</para>
|
||||
/// <para>2 - < min length and only 2 character types [red]</para>
|
||||
/// <para>2 - < min length without complexity - 2 and only 1 character type [red]</para>
|
||||
/// <para>3 - < 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>&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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"> *</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"] </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" })
|
||||
|
||||
<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 |
|
After Width: | Height: | Size: 582 B |
|
After Width: | Height: | Size: 923 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 529 B |
|
After Width: | Height: | Size: 765 B |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 697 B |
|
After Width: | Height: | Size: 1011 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 604 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |