1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-22 02:51:33 +01:00

Tweak provider views (#1499)

* Add Organizations to provider views

Remove enabled/disabled toggle from provider. It's currently not used.

* Remove provider Delete

There are implications to deleting providers on the organizations they manage.
We want to think through this flow before allowing delete from the
admin portal.

* Use toastr to display production exception messages.

Update build actions to upgrade npm to v7.

Use a custom error handler in production which displays a toast of the
exception message and redirect to the offending page

* Clarify provider create error message
This commit is contained in:
Matt Gibson 2021-08-10 12:28:00 -04:00 committed by GitHub
parent 5dc6013e37
commit 842a1c2e37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 6464 additions and 58 deletions

View File

@ -42,6 +42,10 @@ jobs:
with:
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print environment
run: |
nuget help | grep Version
@ -144,6 +148,10 @@ jobs:
with:
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print environment
run: |
whoami

View File

@ -198,6 +198,10 @@ jobs:
with:
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print Environment
run: |
dotnet --info

View File

@ -45,6 +45,10 @@ jobs:
with:
node-version: '14'
- name: Update NPM
run: |
npm install -g npm@7
- name: Print Environment
run: |
dotnet --info

View File

@ -62,7 +62,7 @@ namespace Bit.CommCore.Services
var owner = await _userRepository.GetByEmailAsync(ownerEmail);
if (owner == null)
{
throw new BadRequestException("Invalid owner.");
throw new BadRequestException("Invalid owner. Owner must be an existing Bitwarden user.");
}
var provider = new Provider

View File

@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Admin.Controllers
{
public class ErrorController : Controller
{
[Route("/error")]
public IActionResult Error(int? statusCode = null)
{
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
TempData["Error"] = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error.Message;
if (exceptionHandlerPathFeature != null)
{
return Redirect(exceptionHandlerPathFeature.Path);
}
else
{
return Redirect("/Home");
}
}
}
}

View File

@ -18,20 +18,23 @@ namespace Bit.Admin.Controllers
{
private readonly IProviderRepository _providerRepository;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly GlobalSettings _globalSettings;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IProviderService _providerService;
public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderService providerService, GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService,
GlobalSettings globalSettings, IApplicationCacheService applicationCacheService)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
_providerOrganizationRepository = providerOrganizationRepository;
_providerService = providerService;
_globalSettings = globalSettings;
_applicationCacheService = applicationCacheService;
}
public async Task<IActionResult> Index(string name = null, string userEmail = null, int page = 1, int count = 25)
{
if (page < 1)
@ -57,7 +60,7 @@ namespace Bit.Admin.Controllers
SelfHosted = _globalSettings.SelfHosted
});
}
public IActionResult Create(string ownerEmail = null)
{
return View(new CreateProviderModel
@ -65,7 +68,7 @@ namespace Bit.Admin.Controllers
OwnerEmail = ownerEmail
});
}
[HttpPost]
public async Task<IActionResult> Create(CreateProviderModel model)
{
@ -78,7 +81,7 @@ namespace Bit.Admin.Controllers
return RedirectToAction("Index");
}
public async Task<IActionResult> View(Guid id)
{
var provider = await _providerRepository.GetByIdAsync(id);
@ -88,9 +91,10 @@ namespace Bit.Admin.Controllers
}
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
return View(new ProviderViewModel(provider, users));
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
return View(new ProviderViewModel(provider, users, providerOrganizations));
}
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<IActionResult> Edit(Guid id)
{
@ -101,9 +105,10 @@ namespace Bit.Admin.Controllers
}
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
return View(new ProviderEditModel(provider, users));
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
return View(new ProviderEditModel(provider, users, providerOrganizations));
}
[HttpPost]
[ValidateAntiForgeryToken]
[SelfHosted(NotSelfHostedOnly = true)]
@ -120,19 +125,6 @@ namespace Bit.Admin.Controllers
await _applicationCacheService.UpsertProviderAbilityAsync(provider);
return RedirectToAction("Edit", new { id });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(Guid id)
{
var provider = await _providerRepository.GetByIdAsync(id);
if (provider != null)
{
await _providerRepository.DeleteAsync(provider);
}
return RedirectToAction("Index");
}
public async Task<IActionResult> ResendInvite(Guid ownerId, Guid providerId)
{

View File

@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Bit.Core.Enums.Provider;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table.Provider;
@ -11,29 +9,26 @@ namespace Bit.Admin.Models
{
public ProviderEditModel() { }
public ProviderEditModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers)
: base(provider, providerUsers)
public ProviderEditModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
: base(provider, providerUsers, organizations)
{
Name = provider.Name;
BusinessName = provider.BusinessName;
BillingEmail = provider.BillingEmail;
Enabled = provider.Enabled;
}
public bool Enabled { get; set; }
[Display(Name = "Billing Email")]
public string BillingEmail { get; set; }
[Display(Name = "Business Name")]
public string BusinessName { get; set; }
public string Name { get; set; }
[Display(Name = "Events")]
public Provider ToProvider(Provider existingProvider)
{
existingProvider.Name = Name;
existingProvider.BusinessName = BusinessName;
existingProvider.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
existingProvider.Enabled = Enabled;
return existingProvider;
}
}

View File

@ -10,15 +10,18 @@ namespace Bit.Admin.Models
{
public ProviderViewModel() { }
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers)
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
{
Provider = provider;
UserCount = providerUsers.Count();
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
ProviderOrganizations = organizations.Where(o => o.ProviderId == provider.Id);
}
public int UserCount { get; set; }
public Provider Provider { get; set; }
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
public IEnumerable<ProviderOrganizationOrganizationDetails> ProviderOrganizations { get; set; }
}
}

View File

@ -125,6 +125,10 @@ namespace Bit.Admin
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
}
app.UseStaticFiles();
app.UseRouting();

View File

@ -18,10 +18,6 @@
</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">
@ -41,12 +37,7 @@
</div>
</div>
</form>
@await Html.PartialAsync("Organizations", 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-action="Delete" asp-route-id="@Model.Provider.Id"
onsubmit="return confirm('Are you sure you want to delete this provider (@Model.Provider.Name)?')">
<button class="btn btn-danger" type="submit">Delete</button>
</form>
</div>
</div>

View File

@ -0,0 +1,35 @@
@model ProviderViewModel
<h2>Provider Organizations</h2>
<div class="row">
<div class="col-sm">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
@if (!Model.ProviderOrganizations.Any())
{
<tr>
<td colspan="6">No results to list.</td>
</tr>
}
else
{
@foreach (var org in Model.ProviderOrganizations)
{
<tr>
<td class="align-middle">
<a asp-controller="Organizations" asp-action="Edit"
asp-route-id="@org.OrganizationId">@org.OrganizationName</a>
</td>
</tr>
}
}
</tbody>
</table>
</div>
</div>
</div>

View File

@ -8,7 +8,4 @@
<h2>Information</h2>
@await Html.PartialAsync("_ViewInformation", Model)
@await Html.PartialAsync("Admins", Model)
<form asp-action="Delete" asp-route-id="@Model.Provider.Id"
onsubmit="return confirm('Are you sure you want to delete this provider (@Model.Provider.Name)?')">
<button class="btn btn-danger" type="submit">Delete</button>
</form>
@await Html.PartialAsync("Organizations", Model)

View File

@ -12,10 +12,12 @@
<environment include="Development">
<link rel="stylesheet" href="~/lib/font-awesome/css/font-awesome.css" />
<link rel="stylesheet" href="~/css/site.css" />
<link rel="stylesheet" href="~/lib/toastr/toastr.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/lib/font-awesome/css/font-awesome.min.css" asp-append-version="true" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/lib/toastr/toastr.min.css" />
</environment>
</head>
<body>
@ -102,16 +104,27 @@
</footer>
<environment include="Development">
<script src="~/lib/jquery/jquery.slim.js"></script>
<script src="~/lib/jquery/jquery.js"></script>
<script src="~/lib/popper/popper.js"></script>
<script src="~/lib/bootstrap/js/bootstrap.js"></script>
<script src="~/lib/toastr/toastr.min.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/jquery/jquery.slim.min.js" asp-append-version="true"></script>
<script src="~/lib/jquery/jquery.min.js" asp-append-version="true"></script>
<script src="~/lib/popper/popper.min.js" asp-append-version="true"></script>
<script src="~/lib/bootstrap/js/bootstrap.min.js" asp-append-version="true"></script>
<script src="~/lib/toastr/toastr.min.js" asp-append-version="true"></script>
</environment>
@if (TempData["Error"] != null)
{
<script>
$(document).ready(function () {
toastr.error("@TempData["Error"]")
});
</script>
}
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@ -44,9 +44,13 @@ function lib() {
dest: paths.libDir + 'font-awesome/fonts'
},
{
src: paths.npmDir + 'jquery/dist/jquery.slim*',
src: paths.npmDir + 'jquery/dist/jquery.*',
dest: paths.libDir + 'jquery'
},
{
src: paths.npmDir + 'toastr/build/*',
dest: paths.libDir + 'toastr'
},
];
const tasks = libs.map((lib) => {

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
"gulp-sass": "4.0.1",
"jquery": "3.5.1",
"merge-stream": "1.0.1",
"popper.js": "1.16.1"
"popper.js": "1.16.1",
"toastr": "^2.1.4"
}
}