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:
parent
5dc6013e37
commit
842a1c2e37
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -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
|
||||
|
4
.github/workflows/prod-deploy.yml
vendored
4
.github/workflows/prod-deploy.yml
vendored
@ -198,6 +198,10 @@ jobs:
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g npm@7
|
||||
|
||||
- name: Print Environment
|
||||
run: |
|
||||
dotnet --info
|
||||
|
4
.github/workflows/qa-deploy.yml
vendored
4
.github/workflows/qa-deploy.yml
vendored
@ -45,6 +45,10 @@ jobs:
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: Update NPM
|
||||
run: |
|
||||
npm install -g npm@7
|
||||
|
||||
- name: Print Environment
|
||||
run: |
|
||||
dotnet --info
|
||||
|
@ -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
|
||||
|
24
src/Admin/Controllers/ErrorController.cs
Normal file
24
src/Admin/Controllers/ErrorController.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +125,10 @@ namespace Bit.Admin
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/error");
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
@ -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>
|
||||
|
35
src/Admin/Views/Providers/Organizations.cshtml
Normal file
35
src/Admin/Views/Providers/Organizations.cshtml
Normal 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>
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
6347
src/Admin/package-lock.json
generated
6347
src/Admin/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user