mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 13:08:17 -05:00
[PM-15485] Add provider plan details to provider Admin pages (#5326)
* Add Provider Plan details to Provider Admin pages * Run dotnet format * Thomas' feedback * Updated code ownership * Robert's feedback * Thomas' feedback
This commit is contained in:
parent
288f08da2a
commit
5709ea36f4
@ -16,7 +16,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Billing\Controllers\" />
|
<Folder Include="Billing\Controllers\" />
|
||||||
<Folder Include="Billing\Models\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Choose>
|
<Choose>
|
||||||
|
@ -235,7 +235,8 @@ public class ProvidersController : Controller
|
|||||||
|
|
||||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
||||||
return View(new ProviderViewModel(provider, users, providerOrganizations));
|
var providerPlans = await _providerPlanRepository.GetByProviderId(id);
|
||||||
|
return View(new ProviderViewModel(provider, users, providerOrganizations, providerPlans.ToList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
|
@ -19,7 +19,7 @@ public class ProviderEditModel : ProviderViewModel, IValidatableObject
|
|||||||
IEnumerable<ProviderOrganizationOrganizationDetails> organizations,
|
IEnumerable<ProviderOrganizationOrganizationDetails> organizations,
|
||||||
IReadOnlyCollection<ProviderPlan> providerPlans,
|
IReadOnlyCollection<ProviderPlan> providerPlans,
|
||||||
string gatewayCustomerUrl = null,
|
string gatewayCustomerUrl = null,
|
||||||
string gatewaySubscriptionUrl = null) : base(provider, providerUsers, organizations)
|
string gatewaySubscriptionUrl = null) : base(provider, providerUsers, organizations, providerPlans)
|
||||||
{
|
{
|
||||||
Name = provider.DisplayName();
|
Name = provider.DisplayName();
|
||||||
BusinessName = provider.DisplayBusinessName();
|
BusinessName = provider.DisplayBusinessName();
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Admin.Billing.Models;
|
||||||
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||||
|
using Bit.Core.Billing.Entities;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
|
||||||
namespace Bit.Admin.AdminConsole.Models;
|
namespace Bit.Admin.AdminConsole.Models;
|
||||||
|
|
||||||
@ -8,17 +11,57 @@ public class ProviderViewModel
|
|||||||
{
|
{
|
||||||
public ProviderViewModel() { }
|
public ProviderViewModel() { }
|
||||||
|
|
||||||
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
|
public ProviderViewModel(
|
||||||
|
Provider provider,
|
||||||
|
IEnumerable<ProviderUserUserDetails> providerUsers,
|
||||||
|
IEnumerable<ProviderOrganizationOrganizationDetails> organizations,
|
||||||
|
IReadOnlyCollection<ProviderPlan> providerPlans)
|
||||||
{
|
{
|
||||||
Provider = provider;
|
Provider = provider;
|
||||||
UserCount = providerUsers.Count();
|
UserCount = providerUsers.Count();
|
||||||
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
|
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
|
||||||
|
|
||||||
ProviderOrganizations = organizations.Where(o => o.ProviderId == provider.Id);
|
ProviderOrganizations = organizations.Where(o => o.ProviderId == provider.Id);
|
||||||
|
|
||||||
|
if (Provider.Type == ProviderType.Msp)
|
||||||
|
{
|
||||||
|
var usedTeamsSeats = ProviderOrganizations.Where(po => po.PlanType == PlanType.TeamsMonthly)
|
||||||
|
.Sum(po => po.OccupiedSeats) ?? 0;
|
||||||
|
var teamsProviderPlan = providerPlans.FirstOrDefault(plan => plan.PlanType == PlanType.TeamsMonthly);
|
||||||
|
if (teamsProviderPlan != null && teamsProviderPlan.IsConfigured())
|
||||||
|
{
|
||||||
|
ProviderPlanViewModels.Add(new ProviderPlanViewModel("Teams (Monthly) Subscription", teamsProviderPlan, usedTeamsSeats));
|
||||||
|
}
|
||||||
|
|
||||||
|
var usedEnterpriseSeats = ProviderOrganizations.Where(po => po.PlanType == PlanType.EnterpriseMonthly)
|
||||||
|
.Sum(po => po.OccupiedSeats) ?? 0;
|
||||||
|
var enterpriseProviderPlan = providerPlans.FirstOrDefault(plan => plan.PlanType == PlanType.EnterpriseMonthly);
|
||||||
|
if (enterpriseProviderPlan != null && enterpriseProviderPlan.IsConfigured())
|
||||||
|
{
|
||||||
|
ProviderPlanViewModels.Add(new ProviderPlanViewModel("Enterprise (Monthly) Subscription", enterpriseProviderPlan, usedEnterpriseSeats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Provider.Type == ProviderType.MultiOrganizationEnterprise)
|
||||||
|
{
|
||||||
|
var usedEnterpriseSeats = ProviderOrganizations.Where(po => po.PlanType == PlanType.EnterpriseMonthly)
|
||||||
|
.Sum(po => po.OccupiedSeats).GetValueOrDefault(0);
|
||||||
|
var enterpriseProviderPlan = providerPlans.FirstOrDefault();
|
||||||
|
if (enterpriseProviderPlan != null && enterpriseProviderPlan.IsConfigured())
|
||||||
|
{
|
||||||
|
var planLabel = enterpriseProviderPlan.PlanType switch
|
||||||
|
{
|
||||||
|
PlanType.EnterpriseMonthly => "Enterprise (Monthly) Subscription",
|
||||||
|
PlanType.EnterpriseAnnually => "Enterprise (Annually) Subscription",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
ProviderPlanViewModels.Add(new ProviderPlanViewModel(planLabel, enterpriseProviderPlan, usedEnterpriseSeats));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int UserCount { get; set; }
|
public int UserCount { get; set; }
|
||||||
public Provider Provider { get; set; }
|
public Provider Provider { get; set; }
|
||||||
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
|
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
|
||||||
public IEnumerable<ProviderOrganizationOrganizationDetails> ProviderOrganizations { get; set; }
|
public IEnumerable<ProviderOrganizationOrganizationDetails> ProviderOrganizations { get; set; }
|
||||||
|
public List<ProviderPlanViewModel> ProviderPlanViewModels { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
<h2>Provider Information</h2>
|
<h2>Provider Information</h2>
|
||||||
@await Html.PartialAsync("_ViewInformation", Model)
|
@await Html.PartialAsync("_ViewInformation", Model)
|
||||||
|
@if (Model.ProviderPlanViewModels.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("~/Billing/Views/Providers/ProviderPlans.cshtml", Model.ProviderPlanViewModels)
|
||||||
|
}
|
||||||
@await Html.PartialAsync("Admins", Model)
|
@await Html.PartialAsync("Admins", Model)
|
||||||
<form method="post" id="edit-form">
|
<form method="post" id="edit-form">
|
||||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||||
|
@ -7,5 +7,9 @@
|
|||||||
|
|
||||||
<h2>Information</h2>
|
<h2>Information</h2>
|
||||||
@await Html.PartialAsync("_ViewInformation", Model)
|
@await Html.PartialAsync("_ViewInformation", Model)
|
||||||
|
@if (Model.ProviderPlanViewModels.Any())
|
||||||
|
{
|
||||||
|
@await Html.PartialAsync("ProviderPlans", Model.ProviderPlanViewModels)
|
||||||
|
}
|
||||||
@await Html.PartialAsync("Admins", Model)
|
@await Html.PartialAsync("Admins", Model)
|
||||||
@await Html.PartialAsync("Organizations", Model)
|
@await Html.PartialAsync("Organizations", Model)
|
||||||
|
26
src/Admin/Billing/Models/ProviderPlanViewModel.cs
Normal file
26
src/Admin/Billing/Models/ProviderPlanViewModel.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Bit.Core.Billing.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Admin.Billing.Models;
|
||||||
|
|
||||||
|
public class ProviderPlanViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int PurchasedSeats { get; set; }
|
||||||
|
public int AssignedSeats { get; set; }
|
||||||
|
public int UsedSeats { get; set; }
|
||||||
|
public int RemainingSeats { get; set; }
|
||||||
|
|
||||||
|
public ProviderPlanViewModel(
|
||||||
|
string name,
|
||||||
|
ProviderPlan providerPlan,
|
||||||
|
int usedSeats)
|
||||||
|
{
|
||||||
|
var purchasedSeats = (providerPlan.SeatMinimum ?? 0) + (providerPlan.PurchasedSeats ?? 0);
|
||||||
|
|
||||||
|
Name = name;
|
||||||
|
PurchasedSeats = purchasedSeats;
|
||||||
|
AssignedSeats = providerPlan.AllocatedSeats ?? 0;
|
||||||
|
UsedSeats = usedSeats;
|
||||||
|
RemainingSeats = purchasedSeats - AssignedSeats;
|
||||||
|
}
|
||||||
|
}
|
18
src/Admin/Billing/Views/Providers/ProviderPlans.cshtml
Normal file
18
src/Admin/Billing/Views/Providers/ProviderPlans.cshtml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
@model List<Bit.Admin.Billing.Models.ProviderPlanViewModel>
|
||||||
|
@foreach (var plan in Model)
|
||||||
|
{
|
||||||
|
<h2>@plan.Name</h2>
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-4 col-lg-3">Purchased Seats</dt>
|
||||||
|
<dd class="col-sm-8 col-lg-9">@plan.PurchasedSeats</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4 col-lg-3">Assigned Seats</dt>
|
||||||
|
<dd class="col-sm-8 col-lg-9">@plan.AssignedSeats</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4 col-lg-3">Used Seats</dt>
|
||||||
|
<dd class="col-sm-8 col-lg-9">@plan.UsedSeats</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-4 col-lg-3">Remaining Seats</dt>
|
||||||
|
<dd class="col-sm-8 col-lg-9">@plan.RemainingSeats</dd>
|
||||||
|
</dl>
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ public class ProviderOrganizationOrganizationDetails
|
|||||||
public int? OccupiedSeats { get; set; }
|
public int? OccupiedSeats { get; set; }
|
||||||
public int? Seats { get; set; }
|
public int? Seats { get; set; }
|
||||||
public string Plan { get; set; }
|
public string Plan { get; set; }
|
||||||
|
public PlanType PlanType { get; set; }
|
||||||
public OrganizationStatusType Status { get; set; }
|
public OrganizationStatusType Status { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -35,6 +35,7 @@ public class ProviderOrganizationOrganizationDetailsReadByProviderIdQuery : IQue
|
|||||||
OccupiedSeats = x.o.OrganizationUsers.Count(ou => ou.Status >= 0),
|
OccupiedSeats = x.o.OrganizationUsers.Count(ou => ou.Status >= 0),
|
||||||
Seats = x.o.Seats,
|
Seats = x.o.Seats,
|
||||||
Plan = x.o.Plan,
|
Plan = x.o.Plan,
|
||||||
|
PlanType = x.o.PlanType,
|
||||||
Status = x.o.Status
|
Status = x.o.Status
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ SELECT
|
|||||||
(SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status >= 0) OccupiedSeats,
|
(SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status >= 0) OccupiedSeats,
|
||||||
O.[Seats],
|
O.[Seats],
|
||||||
O.[Plan],
|
O.[Plan],
|
||||||
|
O.[PlanType],
|
||||||
O.[Status]
|
O.[Status]
|
||||||
FROM
|
FROM
|
||||||
[dbo].[ProviderOrganization] PO
|
[dbo].[ProviderOrganization] PO
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
-- Add column 'PlanType'
|
||||||
|
CREATE OR AlTER VIEW [dbo].[ProviderOrganizationOrganizationDetailsView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
PO.[Id],
|
||||||
|
PO.[ProviderId],
|
||||||
|
PO.[OrganizationId],
|
||||||
|
O.[Name] OrganizationName,
|
||||||
|
PO.[Key],
|
||||||
|
PO.[Settings],
|
||||||
|
PO.[CreationDate],
|
||||||
|
PO.[RevisionDate],
|
||||||
|
(SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status = 2) UserCount,
|
||||||
|
(SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status >= 0) OccupiedSeats,
|
||||||
|
O.[Seats],
|
||||||
|
O.[Plan],
|
||||||
|
O.[PlanType],
|
||||||
|
O.[Status]
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderOrganization] PO
|
||||||
|
LEFT JOIN
|
||||||
|
[dbo].[Organization] O ON O.[Id] = PO.[OrganizationId]
|
||||||
|
GO
|
Loading…
x
Reference in New Issue
Block a user