1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-25 14:52:21 -05:00
This commit is contained in:
Jonas Hendrickx 2024-11-15 09:57:41 +01:00
parent 01a69176c5
commit cec601ea59
11 changed files with 213 additions and 153 deletions

View File

@ -1,6 +1,6 @@
@using Bit.Admin.Services @using Bit.Admin.Services
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Bit.Admin.Components.Navigation
@inject IHttpContextAccessor HttpContextAccessor @inject IHttpContextAccessor HttpContextAccessor
@inject SignInManager<IdentityUser> SignInManager @inject SignInManager<IdentityUser> SignInManager
@inject Core.Settings.GlobalSettings GlobalSettings @inject Core.Settings.GlobalSettings GlobalSettings

View File

@ -1,7 +1,8 @@
using Bit.Admin.Enums; using Bit.Admin.Components.Navigation;
using Bit.Admin.Enums;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Bit.Admin.AdminConsole; namespace Bit.Admin.AdminConsole.Components;
public partial class App : ComponentBase public partial class App : ComponentBase
{ {
@ -17,8 +18,7 @@ public partial class App : ComponentBase
var canPromoteAdmin = AccessControlService.UserHasPermission(Permission.Tools_PromoteAdmin); var canPromoteAdmin = AccessControlService.UserHasPermission(Permission.Tools_PromoteAdmin);
var canGenerateLicense = AccessControlService.UserHasPermission(Permission.Tools_GenerateLicenseFile); var canGenerateLicense = AccessControlService.UserHasPermission(Permission.Tools_GenerateLicenseFile);
var canManageTaxRates = AccessControlService.UserHasPermission(Permission.Tools_ManageTaxRates); var canManageTaxRates = AccessControlService.UserHasPermission(Permission.Tools_ManageTaxRates);
var canManageStripeSubscriptions = var canManageStripeSubscriptions = AccessControlService.UserHasPermission(Permission.Tools_ManageStripeSubscriptions);
AccessControlService.UserHasPermission(Permission.Tools_ManageStripeSubscriptions);
var canProcessStripeEvents = AccessControlService.UserHasPermission(Permission.Tools_ProcessStripeEvents); var canProcessStripeEvents = AccessControlService.UserHasPermission(Permission.Tools_ProcessStripeEvents);
var canMigrateProviders = AccessControlService.UserHasPermission(Permission.Tools_MigrateProviders); var canMigrateProviders = AccessControlService.UserHasPermission(Permission.Tools_MigrateProviders);

View File

@ -1,156 +1,124 @@
@page "/organizations2" @page "/organizations2"
@using Bit.Core.Repositories @using Bit.Core.Repositories
@using Bit.Core.Settings @using Bit.Core.Settings
@using Bit.Infrastructure.EntityFramework.AdminConsole.Models @using Bit.Admin.AdminConsole.Components.Shared.Table
@inject IGlobalSettings GlobalSettings @inject IGlobalSettings GlobalSettings
@inject IOrganizationRepository OrganizationRepository @inject IOrganizationRepository OrganizationRepository
<!-- You cannot set the title part of a layout due to the rendering order. Or you have to use JavaScript, which is not
ideal. -->
<BitPage Title="Organizations"> <BitPage Title="Organizations">
<EditForm class="form-inline mb-2" FormName="@SearchFormName" Model="SearchForm" OnSubmit="OnSearchAsync"> <EditForm FormName="@SearchFormName" Model="SearchForm" OnValidSubmit="OnSearchAsync">
<DataAnnotationsValidator/> <div class="form-inline mb-2">
<ValidationSummary/> <DataAnnotationsValidator/>
<label class="sr-only" for="name-filter">Name</label>
<label class="sr-only" for="name-filter">Name</label> <InputText @bind-Value="SearchForm.Name" class="form-control mb-2 mr-2" id="name-filter" placeholder="Name"/>
<InputText @bind-Value="SearchForm.Name" class="form-control mb-2 mr-2" id="name-filter" placeholder="Name"/> <label class="sr-only" for="email-filter">User email</label>
<label class="sr-only" for="email-filter">User email</label> <InputText @bind-Value="SearchForm.Email" class="form-control mb-2 mr-2" id="email-filter" placeholder="User email"/>
<InputText @bind-Value="SearchForm.Email" class="form-control mb-2 mr-2" id="email-filter" placeholder="User email"/> @if (!Model.SelfHosted)
@if (!Model.SelfHosted)
{
<label class="sr-only" asp-for="Paid">Customer</label>
<InputSelect @bind-Value="SearchForm.Paid" class="form-control mb-2 mr-2">
<option value="">-- Customer --</option>
<option value="true">Paid</option>
<option value="false">Freeloader</option>
</InputSelect>
}
<button type="submit" class="btn btn-primary mb-2" title="Search"><i class="fa fa-search"></i> Search</button>
</EditForm>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th style="width: 190px;">Plan</th>
<th style="width: 80px;">Seats</th>
<th style="width: 150px;">Created</th>
<th style="width: 170px; min-width: 170px;">Details</th>
</tr>
</thead>
<tbody>
@if (Model.Items is { Count: > 0 })
{ {
@foreach (var organization in Model.Items) <label class="sr-only" asp-for="Paid">Customer</label>
<InputSelect @bind-Value="SearchForm.Paid" class="form-control mb-2 mr-2">
<option value="">-- Customer --</option>
<option value="true">Paid</option>
<option value="false">Freeloader</option>
</InputSelect>
}
<button type="submit" class="btn btn-primary mb-2" title="Search"><i class="fa fa-search"></i> Search</button>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th style="width: 190px;">Plan</th>
<th style="width: 80px;">Seats</th>
<th style="width: 150px;">Created</th>
<th style="width: 170px; min-width: 170px;">Details</th>
</tr>
</thead>
<tbody>
@if (Model.Items is { Count: > 0 })
{ {
<tr> @foreach (var organization in Model.Items)
<td> {
<a href="@($"/organizations/edit/{organization.Id}")">@organization.DisplayName()</a> <tr>
</td> <td>
<td> <a href="@($"/organizations/edit/{organization.Id}")">@organization.DisplayName()</a>
@organization.Plan </td>
</td> <td>
<td> @organization.Plan
@organization.Seats </td>
</td> <td>
<td> @organization.Seats
<span title="@organization.CreationDate.ToString()"> </td>
@organization.CreationDate.ToShortDateString() <td>
</span> <span title="@organization.CreationDate.ToString()">
</td> @organization.CreationDate.ToShortDateString()
<td> </span>
@if (!GlobalSettings.SelfHosted) </td>
{ <td>
if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) @if (!GlobalSettings.SelfHosted)
{ {
<i class="fa fa-usd fa-lg fa-fw" title="Paid"></i> if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
<i class="fa fa-usd fa-lg fa-fw" title="Paid"></i>
}
else
{
<i class="fa fa-smile-o fa-lg fa-fw text-muted" title="Freeloader"></i>
}
}
@if (organization.MaxStorageGb is > 1)
{
<i class="fa fa-plus-square fa-lg fa-fw"
title="Additional Storage, @(organization.MaxStorageGb - 1) GB">
</i>
} }
else else
{ {
<i class="fa fa-smile-o fa-lg fa-fw text-muted" title="Freeloader"></i> <i class="fa fa-plus-square-o fa-lg fa-fw text-muted"
title="No Additional Storage">
</i>
} }
} @if (organization.Enabled)
@if (organization.MaxStorageGb is > 1) {
{ <i class="fa fa-check-circle fa-lg fa-fw"
<i class="fa fa-plus-square fa-lg fa-fw" title="Enabled, expires @(organization.ExpirationDate?.ToShortDateString() ?? "-")">
title="Additional Storage, @(organization.MaxStorageGb - 1) GB"> </i>
</i> }
} else
else {
{ <i class="fa fa-times-circle-o fa-lg fa-fw text-muted" title="Disabled"></i>
<i class="fa fa-plus-square-o fa-lg fa-fw text-muted" }
title="No Additional Storage"> @if (organization.TwoFactorIsEnabled())
</i> {
} <i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i>
@if (organization.Enabled) }
{ else
<i class="fa fa-check-circle fa-lg fa-fw" {
title="Enabled, expires @(organization.ExpirationDate?.ToShortDateString() ?? "-")"> <i class="fa fa-unlock fa-lg fa-fw text-muted" title="2FA Not Enabled"></i>
</i> }
} </td>
else </tr>
{ }
<i class="fa fa-times-circle-o fa-lg fa-fw text-muted" title="Disabled"></i> }
} else
@if (organization.TwoFactorIsEnabled()) {
{ <tr>
<i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i> <td colspan="5">No results to list.</td>
}
else
{
<i class="fa fa-unlock fa-lg fa-fw text-muted" title="2FA Not Enabled"></i>
}
</td>
</tr> </tr>
} }
} </tbody>
else </table>
{ </div>
<tr>
<td colspan="5">No results to list.</td>
</tr>
}
</tbody>
</table>
</div>
<EditForm FormName="PaginationForm" Model="SearchForm" OnSubmit="OnSearchAsync">
<nav> <nav>
<ul class="pagination"> <ul class="pagination">
@if (Model.Items != null && Model.PreviousPage.HasValue) @if (Model.Items != null)
{ {
<li class="page-item"> <PageLink FormKey="SearchForm.Page" Label="Previous" Page="@Model.PreviousPage" />
<a class="page-link" asp-action="Index" asp-route-page="@Model.PreviousPage.Value" <PageLink FormKey="SearchForm.Page" Label="Next" Page="@Model.NextPage" />
asp-route-count="@Model.Count" asp-route-userEmail="@SearchForm.Email"
asp-route-name="@SearchForm.Name" asp-route-paid="@SearchForm.Paid">
Previous
</a>
</li>
}
else
{
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">Previous</a>
</li>
}
@if (Model.Items != null && Model.NextPage.HasValue)
{
<li class="page-item">
<a class="page-link" asp-action="Index" asp-route-page="@Model.NextPage.Value"
asp-route-count="@Model.Count" asp-route-userEmail="@SearchForm.Email"
asp-route-name="@SearchForm.Name" asp-route-paid="@SearchForm.Paid">
Next
</a>
</li>
}
else
{
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">Next</a>
</li>
} }
</ul> </ul>
</nav> </nav>

View File

@ -0,0 +1,16 @@
@if (Page.HasValue)
{
<li class="page-item">
<button class="page-link" name="@(FormKey!)" value="@Page">
@Label
</button>
</li>
}
else
{
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">
@Label
</a>
</li>
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Components;
namespace Bit.Admin.AdminConsole.Components.Shared.Table;
public partial class PageLink : ComponentBase
{
[Parameter]
public string Label { get; set; }
[Parameter]
public string? FormKey { get; set; }
[Parameter]
public int? Page { get; set; }
}

View File

@ -1,3 +1,18 @@
@attribute [Authorize] @attribute [Authorize]
@using Bit.Admin.Components @using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Mvc.ViewFeatures
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using System.Net.Http
@using System.Net.Http.Json
@using System.Text.Json
@using Bit.Admin.Components;
@using Bit.Admin.Components.Navigation;

View File

@ -1,15 +0,0 @@
@attribute [Authorize]
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Mvc.ViewFeatures
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using System.Net.Http
@using System.Net.Http.Json
@using System.Text.Json

View File

@ -49,3 +49,11 @@ h3 {
.form-check-input { .form-check-input {
margin-top: .45rem; margin-top: .45rem;
} }
input.invalid {
border-color: $danger;
}
.validation-message {
color: $danger;
}

View File

@ -1,5 +1,6 @@
using System.Globalization; using System.Globalization;
using Bit.Admin.AdminConsole; using Bit.Admin.AdminConsole;
using Bit.Admin.AdminConsole.Components;
using Bit.Admin.IdentityServer; using Bit.Admin.IdentityServer;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Settings; using Bit.Core.Settings;

View File

@ -4,6 +4,7 @@
<RootNamespace>Admin.Test</RootNamespace> <RootNamespace>Admin.Test</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="bunit" Version="1.36.0" />
<PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)"> <PackageReference Include="coverlet.collector" Version="$(CoverletCollectorVersion)">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@ -0,0 +1,51 @@
using Bit.Admin.AdminConsole.Components.Shared.Table;
using Bunit;
namespace Admin.Test.AdminConsole.Components.Shared;
public class PageLinkTests : TestContext
{
[Fact]
public void PageLink_Renders_ClickableLinksCorrectlyWhenPageParameterIsSet()
{
// Arrange
const string formKey = "Key";
const string label = "Test";
const int page = 1;
// Act
var cut = RenderComponent<PageLink>(
(nameof(PageLink.FormKey), formKey),
(nameof(PageLink.Label), label),
(nameof(PageLink.Page), page)
);
// Assert
var button = cut.Find("button");
Assert.Equal(formKey, button.Attributes.Single(x => x.Name == "name").Value);
Assert.Equal(label, button.InnerHtml);
Assert.Equal(page.ToString(), button.Attributes.Single(x => x.Name == "value").Value);
}
[Fact]
public void PageLink_Renders_ClickableLinksCorrectlyWhenPageParameterIsNotSet()
{
// Arrange
const string formKey = "Key";
const string label = "Test";
int? page = null;
// Act
var cut = RenderComponent<PageLink>(
(nameof(PageLink.FormKey), formKey),
(nameof(PageLink.Label), label),
(nameof(PageLink.Page), page)
);
// Assert
var disabledButton = cut.Find("a");
Assert.Equal("#", disabledButton.Attributes.Single(x => x.Name == "href").Value);
}
}