mirror of
https://github.com/bitwarden/server.git
synced 2025-07-05 01:52:49 -05:00
[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
This commit is contained in:
@ -195,4 +195,24 @@ public class ProvidersController : Controller
|
|||||||
|
|
||||||
return RedirectToAction("Edit", "Providers", new { id = id });
|
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)
|
||||||
|
{
|
||||||
|
// TODO : Insert logic to create the new Organization entry, create an OrganizationUser entry for the owner and send the invitation email
|
||||||
|
|
||||||
|
return RedirectToAction("Edit", "Providers", new { id = providerId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Entities.Provider;
|
using Bit.Core.Entities.Provider;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Enums.Provider;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
using Bit.SharedWeb.Utilities;
|
||||||
|
|
||||||
namespace Bit.Admin.Models;
|
namespace Bit.Admin.Models;
|
||||||
|
|
||||||
@ -14,6 +16,14 @@ public class OrganizationEditModel : OrganizationViewModel
|
|||||||
{
|
{
|
||||||
public OrganizationEditModel() { }
|
public OrganizationEditModel() { }
|
||||||
|
|
||||||
|
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,
|
public OrganizationEditModel(Organization org, Provider provider, IEnumerable<OrganizationUserUserDetails> orgUsers,
|
||||||
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
|
IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections, IEnumerable<Group> groups,
|
||||||
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
|
IEnumerable<Policy> policies, BillingInfo billingInfo, IEnumerable<OrganizationConnection> connections,
|
||||||
@ -25,7 +35,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
|||||||
|
|
||||||
Name = org.Name;
|
Name = org.Name;
|
||||||
BusinessName = org.BusinessName;
|
BusinessName = org.BusinessName;
|
||||||
BillingEmail = org.BillingEmail;
|
BillingEmail = provider?.Type == ProviderType.Reseller ? provider.BillingEmail : org.BillingEmail;
|
||||||
PlanType = org.PlanType;
|
PlanType = org.PlanType;
|
||||||
Plan = org.Plan;
|
Plan = org.Plan;
|
||||||
Seats = org.Seats;
|
Seats = org.Seats;
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
@model OrganizationEditModel
|
@model OrganizationEditModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Organization: " + Model.Organization.Name;
|
ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Organization.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
|
@await Html.PartialAsync("_OrganizationFormScripts")
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
document.getElementById('teams-trial').addEventListener('click', () => {
|
document.getElementById('teams-trial').addEventListener('click', () => {
|
||||||
if (document.getElementById('@(nameof(Model.PlanType))').value !==
|
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||||
'@((byte)Bit.Core.Enums.PlanType.Free)') {
|
|
||||||
alert('Organization is not on a free plan.');
|
alert('Organization is not on a free plan.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plan
|
// Plan
|
||||||
document.getElementById('@(nameof(Model.PlanType))').value =
|
document.getElementById('@(nameof(Model.PlanType))').value = '@((byte)Bit.Core.Enums.PlanType.TeamsAnnually)';
|
||||||
'@((byte)Bit.Core.Enums.PlanType.TeamsAnnually)';
|
|
||||||
document.getElementById('@(nameof(Model.Plan))').value = 'Teams (Trial)';
|
document.getElementById('@(nameof(Model.Plan))').value = 'Teams (Trial)';
|
||||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||||
@ -38,19 +37,14 @@
|
|||||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('enterprise-trial').addEventListener('click', () => {
|
document.getElementById('enterprise-trial').addEventListener('click', () => {
|
||||||
if (document.getElementById('@(nameof(Model.PlanType))').value !==
|
if (document.getElementById('@(nameof(Model.PlanType))').value !== '@((byte)Bit.Core.Enums.PlanType.Free)') {
|
||||||
'@((byte)Bit.Core.Enums.PlanType.Free)') {
|
|
||||||
alert('Organization is not on a free plan.');
|
alert('Organization is not on a free plan.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plan
|
// Plan
|
||||||
document.getElementById('@(nameof(Model.PlanType))').value =
|
document.getElementById('@(nameof(Model.PlanType))').value = '@((byte)Bit.Core.Enums.PlanType.EnterpriseAnnually)';
|
||||||
'@((byte)Bit.Core.Enums.PlanType.EnterpriseAnnually)';
|
|
||||||
document.getElementById('@(nameof(Model.Plan))').value = 'Enterprise (Trial)';
|
document.getElementById('@(nameof(Model.Plan))').value = 'Enterprise (Trial)';
|
||||||
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
document.getElementById('@(nameof(Model.Seats))').value = '10';
|
||||||
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
document.getElementById('@(nameof(Model.MaxCollections))').value = '';
|
||||||
@ -73,49 +67,12 @@
|
|||||||
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
document.getElementById('@(nameof(Model.LicenseKey))').value = '@Model.RandomLicenseKey';
|
||||||
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
document.getElementById('@(nameof(Model.ExpirationDate))').value = '@Model.FourteenDayExpirationDate';
|
||||||
document.getElementById('@(nameof(Model.SalesAssistedTrialStarted))').value = true;
|
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>
|
</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)
|
@if (Model.Provider != null)
|
||||||
{
|
{
|
||||||
@ -127,218 +84,7 @@
|
|||||||
<h2>Billing Information</h2>
|
<h2>Billing Information</h2>
|
||||||
@await Html.PartialAsync("_BillingInformation",
|
@await Html.PartialAsync("_BillingInformation",
|
||||||
new BillingInformationModel { BillingInfo = Model.BillingInfo, OrganizationId = Model.Organization.Id })
|
new BillingInformationModel { BillingInfo = Model.BillingInfo, OrganizationId = Model.Organization.Id })
|
||||||
<form method="post" id="edit-form">
|
@await Html.PartialAsync("_OrganizationForm", Model)
|
||||||
<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>
|
|
||||||
@if (Model.Provider?.Type == ProviderType.Reseller)
|
|
||||||
{
|
|
||||||
<input type="email" class="form-control" asp-for="Provider.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>
|
|
||||||
<div class="d-flex mt-4">
|
<div class="d-flex mt-4">
|
||||||
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
|
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
|
||||||
<div class="ml-auto d-flex">
|
<div class="ml-auto d-flex">
|
||||||
|
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>
|
@ -12,7 +12,7 @@
|
|||||||
@if (Model.Provider.Type == ProviderType.Reseller)
|
@if (Model.Provider.Type == ProviderType.Reseller)
|
||||||
{
|
{
|
||||||
<div class="float-right text-nowrap">
|
<div class="float-right text-nowrap">
|
||||||
<a asp-controller="Organizations" asp-action="Create" asp-route-providerId="@Model.Provider.Id" class="btn btn-sm btn-primary">New Organization</a>
|
<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>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
|
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