mirror of
https://github.com/bitwarden/server.git
synced 2025-07-05 10:02:47 -05:00
[AC-1081] Merge feature/billing-obfuscation (#2665)
* [EC-1014] Create Organization Status (Pending/Created) (#2610) * [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table * [EC-427] Provider table Type and BillingPhone MySql migrations * [EC-427] Provider table Type and BillingPhone Postgres migrations * [EC-427] Add mysql migration script * [EC-427] Add mysql migration script * [EC-427] Updated Provider sql script to include default column value * [EC-427] Removed default value from Provider.Type column * [EC-427] Changed migration script to include a default value constraint instead of updating the null type * [EC-427] Updated Sql project Provider table script * [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs * [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update] * [EC-427] Adjusting metadata in migration script * [EC-427] Updated Provider sprocs SQL script files * [EC-427] Fixed migration script * [EC-427] Added sqlite migration * [EC-427] Add missing Provider_Update sproc default value * [EC-427] Added missing GO action to migration script * [EC-428] Redirect to Edit after creating Provider * Revert "[EC-428] Redirect to Edit after creating Provider" This reverts commit6347bca1ed
. * [EC-1014] Create OrganizationStatusType and add Status column to Organizations table * [EC-1014] Added EF migrations * [EC-1014] dotnet format * [EC-1014] Changed Organization.Status from SMALLINT to TINYINT * [EC-1014] Set Organization.Status default value = 1 * [EC-1014] Setting Organization.Status default value as 1 * [EC-459 / EC-428] Admin panel: Add Provider Type to list and creation flow (#2593) * [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table * [EC-427] Provider table Type and BillingPhone MySql migrations * [EC-427] Provider table Type and BillingPhone Postgres migrations * [EC-427] Add mysql migration script * [EC-427] Add mysql migration script * [EC-427] Updated Provider sql script to include default column value * [EC-427] Removed default value from Provider.Type column * [EC-427] Changed migration script to include a default value constraint instead of updating the null type * [EC-427] Updated Sql project Provider table script * [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs * [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update] * [EC-427] Adjusting metadata in migration script * [EC-427] Updated Provider sprocs SQL script files * [EC-427] Fixed migration script * [EC-427] Added sqlite migration * [EC-427] Add missing Provider_Update sproc default value * [EC-427] Added missing GO action to migration script * [EC-459] Added Type column to Providers list * [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel * [EC-428] Updated Create Provider view to include new fields * [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller * [EC-428] Added custom validation for Provider fields depending on selected Type * [EC-428] Redirect to Edit after creating Provider * [EC-428] Setting Provider status as Created for Resellers * [EC-428] Redirect on Provider creation depending if self host server * [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync * [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup * [EC-428] Modified Provider views to use DisplayName attribute values * [EC-428] Moved ICreateProviderCommand to Core project * [EC-428] Adding ICreateProviderCommand injection next to IProviderService * [EC-428] Moved CreateProviderCommand to Commercial.Core project * [EC-459] Added Type column to Providers list * [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel * [EC-428] Updated Create Provider view to include new fields * [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller * [EC-428] Added custom validation for Provider fields depending on selected Type * [EC-428] Redirect to Edit after creating Provider * [EC-428] Setting Provider status as Created for Resellers * [EC-428] Redirect on Provider creation depending if self host server * [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync * [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup * [EC-428] Modified Provider views to use DisplayName attribute values * [EC-428] Moved ICreateProviderCommand to Core project * [EC-428] Adding ICreateProviderCommand injection next to IProviderService * [EC-428] Moved CreateProviderCommand to Commercial.Core project * [EC-428] Moved CreateProviderCommand to namespace Bit.Commercial.Core.Providers * [EC-429] Provider details screen updated with Type, BillingPhone and Organization details (#2666) * [EC-430] Admin portal: Update organization information screen (#2672) * [EC-430] Added ProviderOrganizationProviderDetailsView to get Provider details for an Organization * [EC-430] Added Provider information to Organization Edit/View on Admin panel * [EC-430] Remove "Add to Reseller" button * [EC-430] Removed unused property OrganizationEditModel.ClientOwnerEmail * [EC-430] Replaced IProviderOrganizationRepository.GetProviderDetailsByOrganizationAsync with IProviderRepository.GetByOrganizationIdAsync * [EC-430] Deleted ProviderOrganizationProviderDetails and ProviderOrganizationProviderDetailsReadByOrganizationIdQuery * [EC-429] Only show Create/Add Existing Organization buttons for Reseller providers (#2723) * [EC-432] Add existing Organizations to Provider (#2683) * [EC-432] Added ProviderOrganizationUnassignedOrganizationDetails_Search stored procedure * [EC-432] Added IProviderOrganizationRepository.SearchAsync * [EC-432] Created controller ProviderOrganizationsController to assign Organizations to a Provider * [EC-432] Filter existing organizations by plans Enterprise or Team * [EC-432] Existing Organization name links to edit page * [EC-432] EF filtering out existing organizations by plan type enterprise or teams * [EC-432] Creating multiple ProviderOrganization records * [EC-432] Added ProviderOrganizationUnassignedOrganizationDetails_Search stored procedure * [EC-432] Added IProviderOrganizationRepository.SearchAsync * [EC-432] Created controller ProviderOrganizationsController to assign Organizations to a Provider * [EC-432] Filter existing organizations by plans Enterprise or Team * [EC-432] Existing Organization name links to edit page * [EC-432] EF filtering out existing organizations by plan type enterprise or teams * [EC-432] Creating multiple ProviderOrganization records * [EC-432] Renamed migration script and added missing sproc * [EC-432] Saving multiple events for the created ProviderOrganizations * [EC-432] Included unit testing for ProviderService.AddOrganizations and EventService.LogProviderOrganizationEventsAsync * [EC-432] Removed async from NoopEventService.LogProviderOrganizationEventsAsync * [EC-432] Remove unused dependency setup in ProviderServiceTests.AddOrganizations_Success * [EC-432] Renamed AddOrganizations to AddOrganizationsToReseller and removed addingUserId and key arguments * [EC-432] Added DisplayName attributes to ProviderOrganizationViewModel and used them in the view * [EC-432] Reverted changes to input fields * [EC-432] Moved unassigned organizations search to Organizations repo * [EC-432] Moved AddExistingOrganization action to ProvidersController * [EC-432] dotnet format * [EC-432] Fixed unit test issues * [EC-432] Removed unnecessary Html.DisplayNameFor for labels * [EC-432] Renamed OrganizationSearchViewModel to OrganizationUnassignedToProviderSearchViewModel * [EC-432] Modified IEventService.LogProviderOrganizationEventsAsync to receive an IEnumerable as parameter * [EC-432] Updated IProviderOrganizationRepository and replaced CreateWithManyOrganizations method with CreateManyAsync * [EC-432] Deleted ProviderOrganization_CreateWithManyOrganizations * [AC-432] Simplified Organization_UnassignedToProviderSearch query * [AC-432] Removed unnecessary setup * [EC-432] Checking if stored procedure exists before creating * [EC-432] Renamed migration file to recent date * [EC-435] Admin Portal: Add new Organization creation flow UI (#2707) * [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider * [EC-435] Remove logic for creating an organization * [EC-435] Created partial view _OrganizationFormScripts * [EC-435] Remove unused ReferenceEventType * [EC-435] Added TODO comment on Organization Create * [EC-435] Checking if Provider type is Reseller on creating new assigned organization * [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider * [EC-435] Removing unused buttons * [EC-435] Switched hidden fields to form submit route value * [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder * [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController * [EC-435] Fixing bug on saving Organization that would have BillingEmail as null * [EC-435] Added null check to Provider * [EC-435] Moved trial buttons script logic to Edit view * [AC-431] Add new organization invite process (#2737) * [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider * [EC-435] Remove logic for creating an organization * [EC-435] Created partial view _OrganizationFormScripts * [EC-435] Remove unused ReferenceEventType * [EC-435] Added TODO comment on Organization Create * [EC-435] Checking if Provider type is Reseller on creating new assigned organization * [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider * [EC-435] Removing unused buttons * [EC-435] Switched hidden fields to form submit route value * [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder * [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController * [AC-431] Added new ReferenceEventType OrganizationCreatedByAdmin * [AC-431] Added method IOrganizationService.CreateOrganization * [AC-431] Creating new Organization with Pending status and assigning to Provider * [AC-431] Added method to IMailService to send invitation to initialize org * [AC-431] Added methods CreatePendingOrganization and InitPendingOrganization to IOrganizationService * [AC-431] Org invite includes initOrganization parameter * [AC-431] Modified existing Accept organization user action to initialize org * [AC-431] Updated ProvidersController method name * [AC-431] Created OrganizationUserInitInvitedViewModel to link to 'accept-init-organization' url * [AC-431] Added action AcceptInit to OrganizationUsersController * [AC-431] Resend owner invite * [AC-431] dotnet format * [AC-431] Removed unused parameter 'addingUserId' from IProviderService.AddOrganization * [AC-431] Removed setting manual values for CreationDate and RevisionDate * [AC-431] Updated OrganizationService.InitPendingOrganization to throw exceptions when the Organization does not meet the required criteria * [AC-431] Modified OrganizationUserInitInvitedViewModel to inherit properties from OrganizationUserInvitedViewModel * [AC-431] Removed unecessary parameter check * [AC-431] Moved method description to IOrganizationService.InitPendingOrganization * [AC-431] Moved ApplicationCacheService.UpsertOrganizationAbilityAsync and ReferenceEventService.RaiseEventAsync to OrganizationService * [AC-431] Creating collection after creating organization * [EC-435] Fixing bug on saving Organization that would have BillingEmail as null * [AC-431] Deleted OrganizationUserInitInvitedViewModel and added parameter InitOrganization to OrganizationUserInvitedViewModel.cs * [AC-431] Checking if the user has any existing SingleOrg policies before initializing an Org * [AC-431] Remove commented code * [EC-435] Added null check to Provider * [EC-435] Moved trial buttons script logic to Edit view * [AC-431] Added EncryptedString attribute to OrganizationUserAcceptInitRequestModel.CollectionName * [AC-431] Refactored plan check condition * [AC-431] Remove duplicate _applicationCacheService.UpsertOrganizationAbilityAsync call * [AC-431] Removed IMailService.SendOrganizationInitInviteEmailAsync * [AC-431] Added parameters ClaimsPrincipal and IUserService to IOrganizationService.CreatePendingOrganization * [AC-434] Hide Billing screen for Reseller clients (#2783) * [AC-434] Added ProviderType to ProfileOrganizationResponseModel * [AC-434] Migration script * [AC-434] Fixed indentation on migration script * [AC-434] Hiding sensitive subscription data if the user does not have permissions * [AC-434] Fixed missing dependency in unit test * [AC-434] Altered BillingSubscription.Amount and BillingSubscriptionUpcomingInvoice.Amount to nullable * [AC-434] Replaced CurrentContext.ManageBilling with ViewBillingHistory, ViewSubscription, EditSubscription and EditPaymentMethods * [AC-434] Reverted change on BillingSubscription.Amount and now setting Subscription.Items = null when User does not have permission * [AC-434] Added ProviderOrganizationProviderDetails_ReadByUserId * [AC-434] Added IProviderOrganizationRepository.GetManyByUserAsync * [AC-434] Added CurrentContext.GetOrganizationProviderDetails * [AC-434] Remove unneeded join Organization table * [AC-1255] Search Existing Organizations by partial Email (#2830) * [AC-1255] Added email search field input validation * [AC-1255] Reverted added email pattern * [AC-1255] Modified Organization search by Email to search using substring * [AC-1276] Displaying an Organizations pending owners if the Organization is in a Pending status (#2834) * [AC-432] Checking that an existing Organization is not assigned to any Provider before being assigned (#2840) * [AC-432] Checking if any of the selected Organizations is already assigned to a Provider * [AC-432] Changed ProviderOrganization_ReadByOrganizationIds to only get count * [AC-432] Replaced IProviderOrganizationRepository.GetCountByOrganizationIdsAsync with call to IProviderOrganizationRepository.GetByOrganizationId * [AC-432] undo new line * [AC-432] Fixed unit test * Revert "[AC-432] Replaced IProviderOrganizationRepository.GetCountByOrganizationIdsAsync with call to IProviderOrganizationRepository.GetByOrganizationId" This reverts commitee6e095e88
. # Conflicts: # util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql * [AC-432] Created new migration script for ProviderOrganization_ReadCountByOrganizationIds
This commit is contained in:
@ -17,6 +17,7 @@ namespace Bit.Admin.Controllers;
|
||||
[Authorize]
|
||||
public class OrganizationsController : Controller
|
||||
{
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
@ -31,9 +32,11 @@ public class OrganizationsController : Controller
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly ILogger<OrganizationsController> _logger;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
@ -48,8 +51,10 @@ public class OrganizationsController : Controller
|
||||
GlobalSettings globalSettings,
|
||||
IReferenceEventService referenceEventService,
|
||||
IUserService userService,
|
||||
IProviderRepository providerRepository,
|
||||
ILogger<OrganizationsController> logger)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_organizationConnectionRepository = organizationConnectionRepository;
|
||||
@ -64,6 +69,7 @@ public class OrganizationsController : Controller
|
||||
_globalSettings = globalSettings;
|
||||
_referenceEventService = referenceEventService;
|
||||
_userService = userService;
|
||||
_providerRepository = providerRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -103,6 +109,7 @@ public class OrganizationsController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var provider = await _providerRepository.GetByOrganizationIdAsync(id);
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
|
||||
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
|
||||
IEnumerable<Group> groups = null;
|
||||
@ -117,7 +124,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
|
||||
return View(new OrganizationViewModel(organization, billingSyncConnection, users, ciphers, collections, groups, policies));
|
||||
return View(new OrganizationViewModel(organization, provider, billingSyncConnection, users, ciphers, collections, groups, policies));
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
@ -129,6 +136,7 @@ public class OrganizationsController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var provider = await _providerRepository.GetByOrganizationIdAsync(id);
|
||||
var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id);
|
||||
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id);
|
||||
IEnumerable<Group> groups = null;
|
||||
@ -144,7 +152,7 @@ public class OrganizationsController : Controller
|
||||
var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
|
||||
var billingInfo = await _paymentService.GetBillingAsync(organization);
|
||||
var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null;
|
||||
return View(new OrganizationEditModel(organization, users, ciphers, collections, groups, policies,
|
||||
return View(new OrganizationEditModel(organization, provider, users, ciphers, collections, groups, policies,
|
||||
billingInfo, billingSyncConnection, _globalSettings));
|
||||
}
|
||||
|
||||
@ -214,4 +222,21 @@ public class OrganizationsController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ResendOwnerInvite(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, OrganizationUserType.Owner);
|
||||
foreach (var organizationUser in organizationUsers)
|
||||
{
|
||||
await _organizationService.ResendInviteAsync(id, null, organizationUser.Id, true);
|
||||
}
|
||||
|
||||
return Json(null);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Providers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -13,23 +15,42 @@ namespace Bit.Admin.Controllers;
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public class ProvidersController : Controller
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICreateProviderCommand _createProviderCommand;
|
||||
|
||||
public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService,
|
||||
GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
|
||||
public ProvidersController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationService organizationService,
|
||||
IProviderRepository providerRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IProviderService providerService,
|
||||
GlobalSettings globalSettings,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IReferenceEventService referenceEventService,
|
||||
IUserService userService,
|
||||
ICreateProviderCommand createProviderCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationService = organizationService;
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_providerOrganizationRepository = providerOrganizationRepository;
|
||||
_providerService = providerService;
|
||||
_globalSettings = globalSettings;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_referenceEventService = referenceEventService;
|
||||
_userService = userService;
|
||||
_createProviderCommand = createProviderCommand;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
|
||||
@ -75,9 +96,18 @@ public class ProvidersController : Controller
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _providerService.CreateAsync(model.OwnerEmail);
|
||||
var provider = model.ToProvider();
|
||||
switch (provider.Type)
|
||||
{
|
||||
case ProviderType.Msp:
|
||||
await _createProviderCommand.CreateMspAsync(provider, model.OwnerEmail);
|
||||
break;
|
||||
case ProviderType.Reseller:
|
||||
await _createProviderCommand.CreateResellerAsync(provider);
|
||||
break;
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
return RedirectToAction("Edit", new { id = provider.Id });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> View(Guid id)
|
||||
@ -130,4 +160,76 @@ public class ProvidersController : Controller
|
||||
TempData["InviteResentTo"] = ownerId;
|
||||
return RedirectToAction("Edit", new { id = providerId });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> AddExistingOrganization(Guid id, string name = null, string ownerEmail = null, int page = 1, int count = 25)
|
||||
{
|
||||
if (page < 1)
|
||||
{
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var unassignedOrganizations = await _organizationRepository.SearchUnassignedToProviderAsync(name, ownerEmail, skip, count);
|
||||
var viewModel = new OrganizationUnassignedToProviderSearchViewModel
|
||||
{
|
||||
OrganizationName = string.IsNullOrWhiteSpace(name) ? null : name,
|
||||
OrganizationOwnerEmail = string.IsNullOrWhiteSpace(ownerEmail) ? null : ownerEmail,
|
||||
Page = page,
|
||||
Count = count,
|
||||
Items = unassignedOrganizations.Select(uo => new OrganizationSelectableViewModel
|
||||
{
|
||||
Id = uo.Id,
|
||||
Name = uo.Name,
|
||||
PlanType = uo.PlanType
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddExistingOrganization(Guid id, OrganizationUnassignedToProviderSearchViewModel model)
|
||||
{
|
||||
var organizationIds = model.Items.Where(o => o.Selected).Select(o => o.Id).ToArray();
|
||||
if (organizationIds.Any())
|
||||
{
|
||||
await _providerService.AddOrganizationsToReseller(id, organizationIds);
|
||||
}
|
||||
|
||||
return RedirectToAction("Edit", "Providers", new { id = id });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> CreateOrganization(Guid providerId)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
if (provider is not { Type: ProviderType.Reseller })
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
return View(new OrganizationEditModel(provider));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateOrganization(Guid providerId, OrganizationEditModel model)
|
||||
{
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
if (provider is not { Type: ProviderType.Reseller })
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var organization = model.CreateOrganization(provider);
|
||||
await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted);
|
||||
await _providerService.AddOrganization(providerId, organization.Id, null);
|
||||
|
||||
return RedirectToAction("Edit", "Providers", new { id = providerId });
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,59 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.SharedWeb.Utilities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class CreateProviderModel
|
||||
public class CreateProviderModel : IValidatableObject
|
||||
{
|
||||
public CreateProviderModel() { }
|
||||
|
||||
[Display(Name = "Provider Type")]
|
||||
public ProviderType Type { get; set; }
|
||||
|
||||
[Display(Name = "Owner Email")]
|
||||
[Required]
|
||||
public string OwnerEmail { get; set; }
|
||||
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
|
||||
[Display(Name = "Primary Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
|
||||
public virtual Provider ToProvider()
|
||||
{
|
||||
return new Provider()
|
||||
{
|
||||
Type = Type,
|
||||
BusinessName = BusinessName,
|
||||
BillingEmail = BillingEmail?.ToLowerInvariant().Trim()
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case ProviderType.Msp:
|
||||
if (string.IsNullOrWhiteSpace(OwnerEmail))
|
||||
{
|
||||
var ownerEmailDisplayName = nameof(OwnerEmail).GetDisplayAttribute<CreateProviderModel>()?.GetName();
|
||||
yield return new ValidationResult($"The {ownerEmailDisplayName} field is required.");
|
||||
}
|
||||
break;
|
||||
case ProviderType.Reseller:
|
||||
if (string.IsNullOrWhiteSpace(BusinessName))
|
||||
{
|
||||
var businessNameDisplayName = nameof(BusinessName).GetDisplayAttribute<CreateProviderModel>()?.GetName();
|
||||
yield return new ValidationResult($"The {businessNameDisplayName} field is required.");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(BillingEmail))
|
||||
{
|
||||
var billingEmailDisplayName = nameof(BillingEmail).GetDisplayAttribute<CreateProviderModel>()?.GetName();
|
||||
yield return new ValidationResult($"The {billingEmailDisplayName} field is required.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums.Provider;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.SharedWeb.Utilities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
@ -13,18 +16,26 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
{
|
||||
public OrganizationEditModel() { }
|
||||
|
||||
public OrganizationEditModel(Organization org, IEnumerable<OrganizationUserUserDetails> orgUsers,
|
||||
public OrganizationEditModel(Provider provider)
|
||||
{
|
||||
Provider = provider;
|
||||
BillingEmail = provider.Type == ProviderType.Reseller ? provider.BillingEmail : string.Empty;
|
||||
PlanType = Core.Enums.PlanType.TeamsMonthly;
|
||||
Plan = Core.Enums.PlanType.TeamsMonthly.GetDisplayAttribute()?.GetName();
|
||||
}
|
||||
|
||||
public OrganizationEditModel(Organization org, Provider provider, IEnumerable<OrganizationUserUserDetails> orgUsers,
|
||||
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
|
||||
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
|
||||
GlobalSettings globalSettings)
|
||||
: base(org, connections, orgUsers, ciphers, collections, groups, policies)
|
||||
: base(org, provider, connections, orgUsers, ciphers, collections, groups, policies)
|
||||
{
|
||||
BillingInfo = billingInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
|
||||
Name = org.Name;
|
||||
BusinessName = org.BusinessName;
|
||||
BillingEmail = org.BillingEmail;
|
||||
BillingEmail = provider?.Type == ProviderType.Reseller ? provider.BillingEmail : org.BillingEmail;
|
||||
PlanType = org.PlanType;
|
||||
Plan = org.Plan;
|
||||
Seats = org.Seats;
|
||||
@ -60,7 +71,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
public string BraintreeMerchantId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Name")]
|
||||
[Display(Name = "Organization Name")]
|
||||
public string Name { get; set; }
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
@ -124,6 +135,13 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public bool SalesAssistedTrialStarted { get; set; }
|
||||
|
||||
public Organization CreateOrganization(Provider provider)
|
||||
{
|
||||
BillingEmail = provider.BillingEmail;
|
||||
|
||||
return ToOrganization(new Organization());
|
||||
}
|
||||
|
||||
public Organization ToOrganization(Organization existingOrganization)
|
||||
{
|
||||
existingOrganization.Name = Name;
|
||||
|
8
src/Admin/Models/OrganizationSelectableViewModel.cs
Normal file
8
src/Admin/Models/OrganizationSelectableViewModel.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class OrganizationSelectableViewModel : Organization
|
||||
{
|
||||
public bool Selected { get; set; }
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class OrganizationUnassignedToProviderSearchViewModel : PagedModel<OrganizationSelectableViewModel>
|
||||
{
|
||||
[Display(Name = "Organization Name")]
|
||||
public string OrganizationName { get; set; }
|
||||
|
||||
[Display(Name = "Owner Email")]
|
||||
public string OrganizationOwnerEmail { get; set; }
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Entities.Provider;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Vault.Entities;
|
||||
@ -9,11 +10,12 @@ public class OrganizationViewModel
|
||||
{
|
||||
public OrganizationViewModel() { }
|
||||
|
||||
public OrganizationViewModel(Organization org, IEnumerable<OrganizationConnection> connections,
|
||||
public OrganizationViewModel(Organization org, Provider provider, IEnumerable<OrganizationConnection> connections,
|
||||
IEnumerable<OrganizationUserUserDetails> orgUsers, IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
|
||||
IEnumerable<Group> groups, IEnumerable<Policy> policies)
|
||||
{
|
||||
Organization = org;
|
||||
Provider = provider;
|
||||
Connections = connections ?? Enumerable.Empty<OrganizationConnection>();
|
||||
HasPublicPrivateKeys = org.PublicKey != null && org.PrivateKey != null;
|
||||
UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited);
|
||||
@ -24,17 +26,21 @@ public class OrganizationViewModel
|
||||
CollectionCount = collections.Count();
|
||||
GroupCount = groups?.Count() ?? 0;
|
||||
PolicyCount = policies?.Count() ?? 0;
|
||||
var organizationUserStatus = org.Status == OrganizationStatusType.Pending
|
||||
? OrganizationUserStatusType.Invited
|
||||
: OrganizationUserStatusType.Confirmed;
|
||||
Owners = string.Join(", ",
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Owner && u.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Where(u => u.Type == OrganizationUserType.Owner && u.Status == organizationUserStatus)
|
||||
.Select(u => u.Email));
|
||||
Admins = string.Join(", ",
|
||||
orgUsers
|
||||
.Where(u => u.Type == OrganizationUserType.Admin && u.Status == OrganizationUserStatusType.Confirmed)
|
||||
.Where(u => u.Type == OrganizationUserType.Admin && u.Status == organizationUserStatus)
|
||||
.Select(u => u.Email));
|
||||
}
|
||||
|
||||
public Organization Organization { get; set; }
|
||||
public Provider Provider { get; set; }
|
||||
public IEnumerable<OrganizationConnection> Connections { get; set; }
|
||||
public string Owners { get; set; }
|
||||
public string Admins { get; set; }
|
||||
|
@ -14,10 +14,13 @@ public class ProviderEditModel : ProviderViewModel
|
||||
Name = provider.Name;
|
||||
BusinessName = provider.BusinessName;
|
||||
BillingEmail = provider.BillingEmail;
|
||||
BillingPhone = provider.BillingPhone;
|
||||
}
|
||||
|
||||
[Display(Name = "Billing Email")]
|
||||
public string BillingEmail { get; set; }
|
||||
[Display(Name = "Billing Phone Number")]
|
||||
public string BillingPhone { get; set; }
|
||||
[Display(Name = "Business Name")]
|
||||
public string BusinessName { get; set; }
|
||||
public string Name { get; set; }
|
||||
@ -28,6 +31,7 @@ public class ProviderEditModel : ProviderViewModel
|
||||
existingProvider.Name = Name;
|
||||
existingProvider.BusinessName = BusinessName;
|
||||
existingProvider.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
existingProvider.BillingPhone = BillingPhone?.ToLowerInvariant()?.Trim();
|
||||
return existingProvider;
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,20 @@
|
||||
@model OrganizationEditModel
|
||||
@{
|
||||
ViewData["Title"] = "Organization: " + Model.Organization.Name;
|
||||
ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Organization.Name;
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_OrganizationFormScripts")
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
document.getElementById('teams-trial').addEventListener('click', () => {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !==
|
||||
'@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||
alert('Organization is not on a free plan.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Plan
|
||||
document.getElementById('@(nameof(Model.PlanType))').value =
|
||||
'@((byte)Bit.Core.Enums.PlanType.TeamsAnnually)';
|
||||
document.getElementById('@(nameof(Model.PlanType))').value = '@((byte)Bit.Core.Enums.PlanType.TeamsAnnually)';
|
||||
document.getElementById('@(nameof(Model.Plan))').value = 'Teams (Trial)';
|
||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||
@ -38,19 +37,14 @@
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
||||
|
||||
});
|
||||
|
||||
document.getElementById('enterprise-trial').addEventListener('click', () => {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !==
|
||||
'@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||
alert('Organization is not on a free plan.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Plan
|
||||
document.getElementById('@(nameof(Model.PlanType))').value =
|
||||
'@((byte)Bit.Core.Enums.PlanType.EnterpriseAnnually)';
|
||||
document.getElementById('@(nameof(Model.PlanType))').value = '@((byte)Bit.Core.Enums.PlanType.EnterpriseAnnually)';
|
||||
document.getElementById('@(nameof(Model.Plan))').value = 'Enterprise (Trial)';
|
||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||
@ -73,260 +67,24 @@
|
||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
||||
|
||||
});
|
||||
|
||||
document.getElementById('@(nameof(Model.PlanType))').addEventListener('change', () => {
|
||||
const selectEl = document.getElementById('@(nameof(Model.PlanType))');
|
||||
const selectText = selectEl.options[selectEl.selectedIndex].text;
|
||||
document.getElementById('@(nameof(Model.Plan))').value = selectText;
|
||||
});
|
||||
|
||||
document.getElementById('gateway-customer-link').addEventListener('click', () => {
|
||||
const gateway = document.getElementById('@(nameof(Model.Gateway))');
|
||||
const customerId = document.getElementById('@(nameof(Model.GatewayCustomerId))');
|
||||
if (!gateway || gateway.value === '' || !customerId || customerId.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
window.open('https://dashboard.stripe.com/customers/' + customerId.value, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
window.open('https://www.braintreegateway.com/merchants/@(Model.BraintreeMerchantId)/'
|
||||
+ customerId.value, '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('gateway-subscription-link').addEventListener('click', () => {
|
||||
const gateway = document.getElementById('@(nameof(Model.Gateway))');
|
||||
const subId = document.getElementById('@(nameof(Model.GatewaySubscriptionId))');
|
||||
if (!gateway || gateway.value === '' || !subId || subId.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
window.open('https://dashboard.stripe.com/subscriptions/' + subId.value, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
window.open('https://www.braintreegateway.com/merchants/@(Model.BraintreeMerchantId)/' +
|
||||
'subscriptions/' + subId.value, '_blank');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
|
||||
<h1>Organization <small>@Model.Organization.Name</small></h1>
|
||||
<h1>@(Model.Provider != null ? "Client " : string.Empty)Organization <small>@Model.Organization.Name</small></h1>
|
||||
|
||||
@if (Model.Provider != null)
|
||||
{
|
||||
<h2>Provider Relationship</h2>
|
||||
@await Html.PartialAsync("_ProviderInformation", Model.Provider)
|
||||
}
|
||||
<h2>Organization Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
<h2>Billing Information</h2>
|
||||
@await Html.PartialAsync("_BillingInformation",
|
||||
new BillingInformationModel { BillingInfo = Model.BillingInfo, OrganizationId = Model.Organization.Id })
|
||||
<form method="post" id="edit-form">
|
||||
<input asp-for="SalesAssistedTrialStarted" type="hidden">
|
||||
<h2>General</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="Name"></label>
|
||||
<input type="text" class="form-control" asp-for="Name" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" asp-for="Enabled">
|
||||
<label class="form-check-label" asp-for="Enabled"></label>
|
||||
</div>
|
||||
<h2>Business Information</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="BusinessName"></label>
|
||||
<input type="text" class="form-control" asp-for="BusinessName">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Plan</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="PlanType"></label>
|
||||
<select class="form-control" asp-for="PlanType"
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.PlanType>()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="Plan"></label>
|
||||
<input type="text" class="form-control" asp-for="Plan" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="Seats"></label>
|
||||
<input type="number" class="form-control" asp-for="Seats" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="MaxCollections"></label>
|
||||
<input type="number" class="form-control" asp-for="MaxCollections" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="MaxStorageGb"></label>
|
||||
<input type="number" class="form-control" asp-for="MaxStorageGb" min="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="form-group">
|
||||
<label asp-for="MaxAutoscaleSeats"></label>
|
||||
<input type="number" class="form-control" asp-for="MaxAutoscaleSeats" min="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Features</h2>
|
||||
<div class="row mb-3">
|
||||
<div class="col-4">
|
||||
<h3>General</h3>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" asp-for="SelfHost">
|
||||
<label class="form-check-label" asp-for="SelfHost"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="Use2fa">
|
||||
<label class="form-check-label" asp-for="Use2fa"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseApi">
|
||||
<label class="form-check-label" asp-for="UseApi"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseGroups">
|
||||
<label class="form-check-label" asp-for="UseGroups"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UsePolicies">
|
||||
<label class="form-check-label" asp-for="UsePolicies"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseSso">
|
||||
<label class="form-check-label" asp-for="UseSso"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseKeyConnector">
|
||||
<label class="form-check-label" asp-for="UseKeyConnector"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseScim">
|
||||
<label class="form-check-label" asp-for="UseScim"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseDirectory">
|
||||
<label class="form-check-label" asp-for="UseDirectory"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseEvents">
|
||||
<label class="form-check-label" asp-for="UseEvents"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseResetPassword">
|
||||
<label class="form-check-label" asp-for="UseResetPassword"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseCustomPermissions">
|
||||
<label class="form-check-label" asp-for="UseCustomPermissions"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<h3>Password Manager</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseTotp">
|
||||
<label class="form-check-label" asp-for="UseTotp"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UsersGetPremium">
|
||||
<label class="form-check-label" asp-for="UsersGetPremium"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<h3>Secrets Manager</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseSecretsManager">
|
||||
<label class="form-check-label" asp-for="UseSecretsManager"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Licensing</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="LicenseKey"></label>
|
||||
<input type="text" class="form-control" asp-for="LicenseKey">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="ExpirationDate"></label>
|
||||
<input type="datetime-local" class="form-control" asp-for="ExpirationDate">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Billing</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="BillingEmail"></label>
|
||||
<input type="email" class="form-control" asp-for="BillingEmail">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="Gateway"></label>
|
||||
<select class="form-control" asp-for="Gateway"
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.GatewayType>()">
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="GatewayCustomerId"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewayCustomerId">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-secondary" type="button" id="gateway-customer-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="GatewaySubscriptionId"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewaySubscriptionId">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-secondary" type="button" id="gateway-subscription-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@await Html.PartialAsync("_OrganizationForm", Model)
|
||||
<div class="d-flex mt-4">
|
||||
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
|
||||
<div class="ml-auto d-flex">
|
||||
|
@ -6,6 +6,11 @@
|
||||
|
||||
<h1>Organization <small>@Model.Organization.Name</small></h1>
|
||||
|
||||
@if (Model.Provider != null)
|
||||
{
|
||||
<h2>Provider Relationship</h2>
|
||||
@await Html.PartialAsync("_ProviderInformation", Model.Provider)
|
||||
}
|
||||
<h2>Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
@if(GlobalSettings.SelfHosted)
|
||||
|
@ -0,0 +1,9 @@
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model Bit.Core.Entities.Provider.Provider
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4 col-lg-3">Provider Name</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.Name</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Provider Type</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.Type.GetDisplayAttribute()?.GetName())</dd>
|
||||
</dl>
|
97
src/Admin/Views/Providers/AddExistingOrganization.cshtml
Normal file
97
src/Admin/Views/Providers/AddExistingOrganization.cshtml
Normal file
@ -0,0 +1,97 @@
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@model OrganizationUnassignedToProviderSearchViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Add Existing Organization";
|
||||
var providerId = ViewContext.RouteData.Values["id"];
|
||||
}
|
||||
|
||||
<h1>Add Existing Organization</h1>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<form class="form-inline mb-2" method="get" asp-route-id="@providerId">
|
||||
<label class="sr-only" asp-for="OrganizationName"></label>
|
||||
<input type="text" class="form-control mb-2 mr-2 flex-fill" placeholder="@Html.DisplayNameFor(m => m.OrganizationName)" asp-for="OrganizationName" name="name">
|
||||
<label class="sr-only" asp-for="OrganizationOwnerEmail"></label>
|
||||
<input type="email" class="form-control mb-2 mr-2 flex-fill" placeholder="@Html.DisplayNameFor(m => m.OrganizationOwnerEmail)" asp-for="OrganizationOwnerEmail" name="ownerEmail">
|
||||
<button type="submit" class="btn btn-primary mb-2" title="Search" formmethod="get"><i class="fa fa-search"></i> Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" id="select-form" asp-route-id="@providerId">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20px;">All</th>
|
||||
<th>Name</th>
|
||||
<th style="width: 190px;">Plan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (!Model.Items.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="5">No results to list.</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@for (var i = 0; i < Model.Items.Count; i++)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
@Html.HiddenFor(m => Model.Items[i].Id, new { @readonly = "readonly", autocomplete = "off" })
|
||||
@Html.CheckBoxFor(m => Model.Items[i].Selected)
|
||||
</td>
|
||||
<td>@Html.ActionLink(Model.Items[i].Name, "Edit", "Organizations", new { id = Model.Items[i].Id }, new { target = "_blank" })</td>
|
||||
<td>@(Model.Items[i].PlanType.GetDisplayAttribute()?.Name ?? Model.Items[i].PlanType.ToString())</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav>
|
||||
<ul class="pagination">
|
||||
@if (Model.PreviousPage.HasValue)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" asp-action="AddExistingOrganization" asp-route-id="@providerId" asp-route-page="@Model.PreviousPage.Value"
|
||||
asp-route-count="@Model.Count" asp-route-ownerEmail="@Model.OrganizationOwnerEmail"
|
||||
asp-route-name="@Model.OrganizationName">Previous</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
}
|
||||
@if (Model.NextPage.HasValue)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" asp-action="AddExistingOrganization" asp-route-id="@providerId" asp-route-page="@Model.NextPage.Value"
|
||||
asp-route-count="@Model.Count" asp-route-ownerEmail="@Model.OrganizationOwnerEmail"
|
||||
asp-route-name="@Model.OrganizationName">Next</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Next</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary" form="select-form">Add to Reseller</button>
|
||||
</div>
|
||||
</div>
|
@ -1,16 +1,55 @@
|
||||
@model CreateProviderModel
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model CreateProviderModel
|
||||
@{
|
||||
ViewData["Title"] = "Create Provider";
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function toggleProviderTypeInfo(value) {
|
||||
document.querySelectorAll('[id^="info-"]').forEach(el => { el.classList.add('d-none'); });
|
||||
document.getElementById('info-' + value).classList.remove('d-none');
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
<h1>Create Provider</h1>
|
||||
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label asp-for="OwnerEmail"></label>
|
||||
<input type="text" class="form-control" asp-for="OwnerEmail">
|
||||
<label asp-for="Type" class="h2"></label>
|
||||
@foreach(ProviderType providerType in Enum.GetValues(typeof(ProviderType)))
|
||||
{
|
||||
var providerTypeValue = (int)providerType;
|
||||
<div class="form-check">
|
||||
@Html.RadioButtonFor(m => m.Type, providerType, new { id = $"providerType-{providerTypeValue}", @class = "form-check-input", onclick=$"toggleProviderTypeInfo({providerTypeValue})" })
|
||||
@Html.LabelFor(m => m.Type, providerType.GetDisplayAttribute()?.GetName(), new { @class = "form-check-label align-middle", @for = $"providerType-{providerTypeValue}" })
|
||||
<br/>
|
||||
@Html.LabelFor(m => m.Type, providerType.GetDisplayAttribute()?.GetDescription(), new { @class = "form-check-label small text-muted ml-3 align-top", @for = $"providerType-{providerTypeValue}" })
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="@($"info-{(int)ProviderType.Msp}")" class="form-group @(Model.Type != ProviderType.Msp ? "d-none" : string.Empty)">
|
||||
<h2>MSP Info</h2>
|
||||
<div class="form-group">
|
||||
<label asp-for="OwnerEmail"></label>
|
||||
<input type="text" class="form-control" asp-for="OwnerEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="@($"info-{(int)ProviderType.Reseller}")" class="form-group @(Model.Type != ProviderType.Reseller ? "d-none" : string.Empty)">
|
||||
<h2>Reseller Info</h2>
|
||||
<div class="form-group">
|
||||
<label asp-for="BusinessName"></label>
|
||||
<input type="text" class="form-control" asp-for="BusinessName">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="BillingEmail"></label>
|
||||
<input type="text" class="form-control" asp-for="BillingEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-2">Create Provider</button>
|
||||
|
21
src/Admin/Views/Providers/CreateOrganization.cshtml
Normal file
21
src/Admin/Views/Providers/CreateOrganization.cshtml
Normal file
@ -0,0 +1,21 @@
|
||||
@model OrganizationEditModel
|
||||
@{
|
||||
ViewData["Title"] = "Create Client Organization";
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_OrganizationFormScripts")
|
||||
}
|
||||
|
||||
<h1>New Client Organization</h1>
|
||||
|
||||
@await Html.PartialAsync("_OrganizationForm", Model)
|
||||
<div class="d-flex mt-4">
|
||||
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
|
||||
<div class="ml-auto d-flex">
|
||||
<form asp-controller="Providers" asp-action="Edit" asp-route-id="@Model.Provider.Id"
|
||||
onsubmit="return confirm('Are you sure you want to cancel?')">
|
||||
<button class="btn btn-outline-secondary" type="submit">Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -36,6 +36,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="BillingPhone"></label>
|
||||
<input type="tel" class="form-control" asp-for="BillingPhone">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@await Html.PartialAsync("Organizations", Model)
|
||||
<div class="d-flex mt-4">
|
||||
|
@ -1,4 +1,5 @@
|
||||
@model ProvidersModel
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model ProvidersModel
|
||||
@{
|
||||
ViewData["Title"] = "Providers";
|
||||
}
|
||||
@ -25,6 +26,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="width: 190px;">Provider Type</th>
|
||||
<th style="width: 190px;">Status</th>
|
||||
<th style="width: 150px;">Created</th>
|
||||
</tr>
|
||||
@ -44,6 +46,7 @@
|
||||
<td>
|
||||
<a asp-action="@Model.Action" asp-route-id="@provider.Id">@(provider.Name ?? "Pending")</a>
|
||||
</td>
|
||||
<td>@provider.Type.GetDisplayAttribute()?.GetShortName()</td>
|
||||
<td>@provider.Status</td>
|
||||
<td>
|
||||
<span title="@provider.CreationDate.ToString()">
|
||||
|
@ -1,4 +1,7 @@
|
||||
@model ProviderViewModel
|
||||
|
||||
@await Html.PartialAsync("_ProviderScripts")
|
||||
|
||||
<h2>Provider Organizations</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
@ -6,7 +9,17 @@
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="width: 50%;">Name</th>
|
||||
<th style="width: 50%;">Status</th>
|
||||
<th>
|
||||
@if (Model.Provider.Type == ProviderType.Reseller)
|
||||
{
|
||||
<div class="float-right text-nowrap">
|
||||
<a asp-controller="Providers" asp-action="CreateOrganization" asp-route-providerId="@Model.Provider.Id" class="btn btn-sm btn-primary">New Organization</a>
|
||||
<a asp-controller="Providers" asp-action="AddExistingOrganization" asp-route-id="@Model.Provider.Id" class="btn btn-sm btn-outline-primary">Add Existing Organization</a>
|
||||
</div>
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -22,8 +35,24 @@
|
||||
{
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
<a asp-controller="Organizations" asp-action="Edit"
|
||||
asp-route-id="@org.OrganizationId">@org.OrganizationName</a>
|
||||
<a asp-controller="Organizations" asp-action="Edit" asp-route-id="@org.OrganizationId">@org.OrganizationName</a>
|
||||
</td>
|
||||
<td>
|
||||
@org.Status
|
||||
</td>
|
||||
<td>
|
||||
<div class="float-right">
|
||||
@if (org.Status == OrganizationStatusType.Pending)
|
||||
{
|
||||
<a href="#" class="float-right" onclick="return resendOwnerInvite('@org.OrganizationId', '@org.OrganizationName');">
|
||||
<i class="fa fa-envelope-o fa-lg" title="Resend Setup Invite"></i>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-envelope-o fa-lg text-secondary"></i>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
20
src/Admin/Views/Providers/_ProviderScripts.cshtml
Normal file
20
src/Admin/Views/Providers/_ProviderScripts.cshtml
Normal file
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
function resendOwnerInvite(orgId, orgName) {
|
||||
if (confirm('Resend invite to "' + orgName + '"?')) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '@Url.Action("ResendOwnerInvite", "Organizations")' + '?id=' + orgId,
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (response) {
|
||||
alert('Invitation has been resent!');
|
||||
},
|
||||
error: function (response) {
|
||||
alert("Error!");
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
@ -1,4 +1,5 @@
|
||||
@model ProviderViewModel
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model ProviderViewModel
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4 col-lg-3">Id</dt>
|
||||
<dd class="col-sm-8 col-lg-9"><code>@Model.Provider.Id</code></dd>
|
||||
@ -7,7 +8,10 @@
|
||||
<dd class="col-sm-8 col-lg-9">@Model.Provider.Status</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Users</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.UserCount</dd>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.Provider.Type == ProviderType.Reseller ? "N/A" : Model.UserCount)</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Provider Type</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@(Model.Provider.Type.GetDisplayAttribute()?.GetName())</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Created</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.Provider.CreationDate.ToString()</dd>
|
||||
|
247
src/Admin/Views/Shared/_OrganizationForm.cshtml
Normal file
247
src/Admin/Views/Shared/_OrganizationForm.cshtml
Normal file
@ -0,0 +1,247 @@
|
||||
@using Bit.SharedWeb.Utilities
|
||||
@model OrganizationEditModel
|
||||
|
||||
<form method="post" id="edit-form" asp-route-providerId="@Model.Provider?.Id">
|
||||
<input asp-for="SalesAssistedTrialStarted" type="hidden">
|
||||
<h2>General</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="Name"></label>
|
||||
<input type="text" class="form-control" asp-for="Name" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Provider?.Type == ProviderType.Reseller)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label>Client Owner Email</label>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Owners))
|
||||
{
|
||||
<input type="text" class="form-control" asp-for="Owners" readonly="readonly">
|
||||
}
|
||||
else
|
||||
{
|
||||
<input type="text" class="form-control" asp-for="Owners" required>
|
||||
}
|
||||
<label class="form-check-label small text-muted align-top">This user should be independent of the Provider. If the Provider is disassociated with the organization, this user will maintain ownership of the organization.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (Model.Organization != null)
|
||||
{
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" asp-for="Enabled">
|
||||
<label class="form-check-label" asp-for="Enabled"></label>
|
||||
</div>
|
||||
}
|
||||
<h2>Business Information</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="BusinessName"></label>
|
||||
<input type="text" class="form-control" asp-for="BusinessName">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Plan</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="PlanType"></label>
|
||||
@{
|
||||
var planTypes = Enum.GetValues<PlanType>()
|
||||
.Where(p => Model.Provider == null || p is >= PlanType.TeamsMonthly and <= PlanType.EnterpriseAnnually)
|
||||
.Select(e => new SelectListItem
|
||||
{
|
||||
Value = ((int)e).ToString(),
|
||||
Text = e.GetDisplayAttribute()?.GetName() ?? e.ToString()
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
<select class="form-control" asp-for="PlanType" asp-items="planTypes"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="Plan"></label>
|
||||
<input type="text" class="form-control" asp-for="Plan" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="Seats"></label>
|
||||
<input type="number" class="form-control" asp-for="Seats" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="MaxCollections"></label>
|
||||
<input type="number" class="form-control" asp-for="MaxCollections" min="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="MaxStorageGb"></label>
|
||||
<input type="number" class="form-control" asp-for="MaxStorageGb" min="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<div class="form-group">
|
||||
<label asp-for="MaxAutoscaleSeats"></label>
|
||||
<input type="number" class="form-control" asp-for="MaxAutoscaleSeats" min="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Features</h2>
|
||||
<div class="row mb-3">
|
||||
<div class="col-4">
|
||||
<h3>General</h3>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" class="form-check-input" asp-for="SelfHost">
|
||||
<label class="form-check-label" asp-for="SelfHost"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="Use2fa">
|
||||
<label class="form-check-label" asp-for="Use2fa"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseApi">
|
||||
<label class="form-check-label" asp-for="UseApi"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseGroups">
|
||||
<label class="form-check-label" asp-for="UseGroups"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UsePolicies">
|
||||
<label class="form-check-label" asp-for="UsePolicies"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseSso">
|
||||
<label class="form-check-label" asp-for="UseSso"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseKeyConnector">
|
||||
<label class="form-check-label" asp-for="UseKeyConnector"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseScim">
|
||||
<label class="form-check-label" asp-for="UseScim"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseDirectory">
|
||||
<label class="form-check-label" asp-for="UseDirectory"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseEvents">
|
||||
<label class="form-check-label" asp-for="UseEvents"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseResetPassword">
|
||||
<label class="form-check-label" asp-for="UseResetPassword"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseCustomPermissions">
|
||||
<label class="form-check-label" asp-for="UseCustomPermissions"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<h3>Password Manager</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseTotp">
|
||||
<label class="form-check-label" asp-for="UseTotp"></label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UsersGetPremium">
|
||||
<label class="form-check-label" asp-for="UsersGetPremium"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<h3>Secrets Manager</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseSecretsManager">
|
||||
<label class="form-check-label" asp-for="UseSecretsManager"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Licensing</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="LicenseKey"></label>
|
||||
<input type="text" class="form-control" asp-for="LicenseKey">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="ExpirationDate"></label>
|
||||
<input type="datetime-local" class="form-control" asp-for="ExpirationDate">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Billing</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="BillingEmail"></label>
|
||||
@if (Model.Provider?.Type == ProviderType.Reseller)
|
||||
{
|
||||
<input type="email" class="form-control" asp-for="BillingEmail" readonly="readonly">
|
||||
}
|
||||
else
|
||||
{
|
||||
<input type="email" class="form-control" asp-for="BillingEmail">
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<label asp-for="Gateway"></label>
|
||||
<select class="form-control" asp-for="Gateway"
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.GatewayType>()">
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="GatewayCustomerId"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewayCustomerId">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-secondary" type="button" id="gateway-customer-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="form-group">
|
||||
<label asp-for="GatewaySubscriptionId"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewaySubscriptionId">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-secondary" type="button" id="gateway-subscription-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
35
src/Admin/Views/Shared/_OrganizationFormScripts.cshtml
Normal file
35
src/Admin/Views/Shared/_OrganizationFormScripts.cshtml
Normal file
@ -0,0 +1,35 @@
|
||||
<script>
|
||||
(() => {
|
||||
document.getElementById('@(nameof(Model.PlanType))').addEventListener('change', () => {
|
||||
const selectEl = document.getElementById('@(nameof(Model.PlanType))');
|
||||
const selectText = selectEl.options[selectEl.selectedIndex].text;
|
||||
document.getElementById('@(nameof(Model.Plan))').value = selectText;
|
||||
});
|
||||
document.getElementById('gateway-customer-link').addEventListener('click', () => {
|
||||
const gateway = document.getElementById('@(nameof(Model.Gateway))');
|
||||
const customerId = document.getElementById('@(nameof(Model.GatewayCustomerId))');
|
||||
if (!gateway || gateway.value === '' || !customerId || customerId.value === '') {
|
||||
return;
|
||||
}
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
window.open('https://dashboard.stripe.com/customers/' + customerId.value, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
window.open('https://www.braintreegateway.com/merchants/@(Model.BraintreeMerchantId)/'
|
||||
+ customerId.value, '_blank');
|
||||
}
|
||||
});
|
||||
document.getElementById('gateway-subscription-link').addEventListener('click', () => {
|
||||
const gateway = document.getElementById('@(nameof(Model.Gateway))');
|
||||
const subId = document.getElementById('@(nameof(Model.GatewaySubscriptionId))');
|
||||
if (!gateway || gateway.value === '' || !subId || subId.value === '') {
|
||||
return;
|
||||
}
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
window.open('https://dashboard.stripe.com/subscriptions/' + subId.value, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
window.open('https://www.braintreegateway.com/merchants/@(Model.BraintreeMerchantId)/' +
|
||||
'subscriptions/' + subId.value, '_blank');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
Reference in New Issue
Block a user