MRB webassembly

This commit is contained in:
Chase Tucker
2024-05-13 14:33:27 -07:00
parent ba8d92ea01
commit 9b7e3ef897
109 changed files with 11731 additions and 1024 deletions

View File

@ -0,0 +1,26 @@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" >
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true) {
<RedirectToLogin />
} else {
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
<Authorizing>
<MudProgressCircular Color="Color.Tertiary" Indeterminate="true" />
<div>Authorizing...</div>
</Authorizing>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

View File

@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>", Scope = "member", Target = "~M:MesaFabApproval.Client.Services.AuthenticationService.AttemptLocalUserAuth~System.Threading.Tasks.Task{System.Security.Claims.ClaimsPrincipal}")]

View File

@ -0,0 +1,71 @@
@inherits LayoutComponentBase
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject IConfiguration Configuration
@inject IMemoryCache cache
@inject NavigationManager navManager
<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<MudPopoverProvider />
<div style="height: 100vh;">
<MudLayout>
<MudAppBar Elevation="1" Color="Color.Info">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
<MudText Typo="Typo.h5" Class="ml-3">Mesa Fab Approval</MudText>
@if (authStateProvider.CurrentUser is not null) {
<MudSpacer />
<MudText Typo="Typo.h6" Class="mr-3">@authStateProvider.CurrentUser.FirstName @authStateProvider.CurrentUser.LastName</MudText>
<MudIconButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick=Logout
Edge="Edge.End"
Icon="@Icons.Material.Filled.Logout" />
}
</MudAppBar>
<MudDrawer @bind-Open="_drawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2">
<MudNavMenu Color="Color.Info" Bordered="true" Class="d-flex flex-column justify-center p-1 gap-1">
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Href="@Configuration["OldFabApprovalUrl"]"
Target="_blank"
StartIcon="@Icons.Material.Filled.Home">
Return to Main Site
</MudButton>
<MudDivider Class="my-1" />
@if (authStateProvider.CurrentUser is not null) {
<MudNavGroup Title="Create New">
<MudNavLink OnClick="@(() => GoTo("mrb/new"))">Create New MRB</MudNavLink>
</MudNavGroup>
<MudNavLink OnClick="@(() => GoTo(""))" Icon="@Icons.Material.Filled.Dashboard">Dashboard</MudNavLink>
<MudNavLink OnClick="@(() => GoTo("mrb/all"))" Icon="@Icons.Material.Filled.Ballot">MRB List</MudNavLink>
}
</MudNavMenu>
</MudDrawer>
<div style="display: flex; flex-flow: column; height: 100%;">
<MudMainContent Style="@($"background:#E0E0E0; flex-grow: 1;")">
@Body
</MudMainContent>
</div>
</MudLayout>
</div>
@code {
bool _drawerOpen = true;
void DrawerToggle() {
_drawerOpen = !_drawerOpen;
}
void Logout() {
authStateProvider.Logout();
}
private void GoTo(string page) {
DrawerToggle();
cache.Set("redirectUrl", page);
navManager.NavigateTo(page);
}
}

View File

@ -0,0 +1,77 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Watch Include="**\*.razor" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.8" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageReference Include="MudBlazor" Version="7.6.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MesaFabApproval.Shared\MesaFabApproval.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,64 @@
@page "/redirect"
@attribute [AllowAnonymous]
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject IAuthenticationService authService
@inject IUserService userService
@inject ISnackbar snackbar
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navigationManager
@code {
private string? _jwt;
private string? _refreshToken;
private string? _redirectPath;
protected override async Task OnParametersSetAsync() {
try {
Uri uri = navigationManager.ToAbsoluteUri(navigationManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("jwt", out var jwt)) {
_jwt = System.Net.WebUtility.UrlDecode(jwt);
}
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("refreshToken", out var refreshToken)) {
_refreshToken = System.Net.WebUtility.UrlDecode(refreshToken);
}
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("redirectPath", out var redirectPath)) {
_redirectPath = System.Net.WebUtility.UrlDecode(redirectPath);
}
if (!string.IsNullOrWhiteSpace(_jwt) && !string.IsNullOrWhiteSpace(_refreshToken)) {
await authService.SetTokens(_jwt, _refreshToken);
ClaimsPrincipal principal = authService.GetClaimsPrincipalFromJwt(_jwt);
string loginId = userService.GetLoginIdFromClaimsPrincipal(principal);
await authService.SetLoginId(loginId);
await authService.SetTokens(_jwt, _refreshToken);
User? user = await userService.GetUserByLoginId(loginId);
await authService.SetCurrentUser(user);
await authStateProvider.StateHasChanged(principal);
}
if (authStateProvider.CurrentUser is not null && !string.IsNullOrWhiteSpace(_redirectPath)) {
navigationManager.NavigateTo(_redirectPath);
} else {
await authStateProvider.Logout();
if (!string.IsNullOrWhiteSpace(_redirectPath)) {
navigationManager.NavigateTo($"login/{_redirectPath}");
} else {
navigationManager.NavigateTo("login");
}
}
} catch (Exception ex) {
snackbar.Add($"Redirect failed, because {ex.Message}", Severity.Error);
navigationManager.NavigateTo("login");
}
}
}

View File

@ -0,0 +1,69 @@
@inject ISnackbar snackbar
<MudDialog>
<DialogContent>
<MudPaper Class="p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="Comments"
Required="true"
RequiredError="You must provide a comment"
@bind-Value="@comments"
@bind-Text="@comments"
Immediate="true"
AutoGrow
AutoFocus/>
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=SubmitComments>
@if (processing) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Ok</MudText>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter]
public string comments { get; set; } = "";
private string[] errors = { };
private bool processing = false;
protected override void OnParametersSet() {
comments = string.Empty;
}
private void SubmitComments() {
processing = true;
try {
if (string.IsNullOrWhiteSpace(comments) || comments.Length < 5)
throw new Exception("Comments must be at least five characters long");
MudDialog.Close(DialogResult.Ok(comments));
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
processing = false;
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
}

View File

@ -0,0 +1,115 @@
@inject ISnackbar snackbar
@inject IMRBService mrbService
@inject MesaFabApprovalAuthStateProvider authStateProvider
<MudDialog>
<DialogContent>
<MudPaper Class="p-2">
<MudForm @bind-Errors="@errors">
<MudTextField T="string"
Label="Comments"
Required="true"
RequiredError="You must provide a comment"
@bind-Value="@comments"
@bind-Text="@comments"
Immediate="true"
AutoGrow
AutoFocus/>
<MudFileUpload T="IReadOnlyList<IBrowserFile>" OnFilesChanged="AddAttachments">
<ActivatorContent>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
style="margin: auto;"
StartIcon="@Icons.Material.Filled.AttachFile">
@if (attachmentUploadInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Upload Supporting Document</MudText>
}
</MudButton>
</ActivatorContent>
</MudFileUpload>
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=SubmitComments>
@if (processing) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Ok</MudText>
}
</MudButton>
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter]
public string comments { get; set; } = "";
[Parameter]
public int actionId { get; set; } = 0;
private string[] errors = { };
private bool processing = false;
private bool attachmentUploadInProcess = false;
protected override void OnParametersSet() {
comments = string.Empty;
}
private void SubmitComments() {
processing = true;
try {
if (string.IsNullOrWhiteSpace(comments) || comments.Length < 5)
throw new Exception("Comments must be at least five characters long");
MudDialog.Close(DialogResult.Ok(comments));
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
processing = false;
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
private async Task AddAttachments(InputFileChangeEventArgs args) {
attachmentUploadInProcess = true;
try {
if (actionId <= 0)
throw new Exception($"{actionId} is not a valid MRB action ID");
IReadOnlyList<IBrowserFile> attachments = args.GetMultipleFiles();
if (authStateProvider.CurrentUser is not null) {
await mrbService.UploadActionAttachments(attachments, actionId);
await mrbService.GetAllActionAttachmentsForMRB(actionId, true);
attachmentUploadInProcess = false;
snackbar.Add("Attachments successfully uploaded", Severity.Success);
StateHasChanged();
}
} catch (Exception ex) {
attachmentUploadInProcess = false;
snackbar.Add($"Unable to upload attachments, because {ex.Message}", Severity.Error);
}
}
}

View File

@ -0,0 +1,261 @@
@inject IMRBService mrbService
@inject ISnackbar snackbar
@inject ICustomerService customerService
<MudDialog>
<DialogContent>
<MudPaper Class="p-2">
<MudForm @bind-Errors="@errors">
<MudSelect T="string"
Label="Action"
Required="true"
RequiredError="You must select an action!"
@bind-Value="@mrbAction.Action"
Text="@mrbAction.Action">
<MudSelectItem Value="@("Block")" />
<MudSelectItem Value="@("Convert")" />
<MudSelectItem Value="@("Recall")" />
<MudSelectItem Value="@("Scrap")" />
<MudSelectItem Value="@("Unblock")" />
<MudSelectItem Value="@("Waiver")" />
</MudSelect>
@if (mrbAction.Action.Equals("Convert")) {
<MudTextField @bind-Value="@mrbAction.ConvertFrom"
Label="Convert From"
Required
RequiredError="Conversion value required!"
Text="@mrbAction.ConvertFrom" />
<MudTextField @bind-Value="@mrbAction.ConvertTo"
Label="Convert To"
Required
RequiredError="Conversion value required!"
Text="@mrbAction.ConvertTo" />
}
<MudSelect T="string"
Label="Affected Customer"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.Customer
Text="@mrbAction.Customer">
@foreach (string customer in customerNames) {
<MudSelectItem Value="@(customer)" />
}
</MudSelect>
<MudTextField T="int"
InputType="@InputType.Number"
Label="Qty"
Required="true"
RequiredError="You must supply a quantity!"
@bind-Value=mrbAction.Quantity
Text="@mrbAction.Quantity.ToString()" />
<MudAutocomplete T="string"
Label="Part Number"
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.PartNumber
Text="@mrbAction.PartNumber"
SearchFunc="@PartNumberSearch"
ResetValueOnEmptyText
CoerceText />
<MudAutocomplete T="string"
Label="Batch Number / Lot Number"
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.LotNumber
Text="@mrbAction.LotNumber"
SearchFunc="@LotNumberSearch"
ResetValueOnEmptyText
CoerceText />
@if (mrbAction.Action.Equals("Scrap")) {
<MudSelect T="string"
Label="Justification"
Required
RequiredError="Justification required!"
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=mrbAction.Justification
Text="@mrbAction.Justification">
<MudSelectItem Value="@("Obsolete")" />
<MudSelectItem Value="@("Aged Material")" />
<MudSelectItem Value="@("Out of Specification")" />
</MudSelect>
}
</MudForm>
</MudPaper>
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=SaveMRBAction>
@if (processingSave) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Save</MudText>
}
</MudButton>
@if (mrbAction is not null && mrbAction.ActionID > 0) {
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
Disabled="@(mrbAction is null || (mrbAction is not null && mrbAction.ActionID <= 0))"
Class="m1-auto"
OnClick=DeleteMRBAction>
@if (processingDelete) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Delete</MudText>
}
</MudButton>
}
<MudButton Variant="Variant.Filled"
Class="grey text-black m1-auto"
OnClick=Cancel>
Cancel
</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter]
public MRBAction mrbAction { get; set; }
private IEnumerable<MRBAction>? actions = null;
private MRBAction lastAction = null;
private IEnumerable<string> customerNames = new List<string>();
private bool isVisible { get; set; }
private string[] errors = { };
private bool processingSave = false;
private bool processingDelete = false;
protected override async Task OnParametersSetAsync() {
isVisible = true;
if (mrbAction is null) {
snackbar.Add("MRB action cannot be null", Severity.Warning);
MudDialog.Cancel();
} else {
actions = (await mrbService.GetMRBActionsForMRB(mrbAction.MRBNumber, false)).OrderByDescending(a => a.ActionID);
if (actions is not null && actions.Count() > 0) {
if (string.IsNullOrWhiteSpace(mrbAction.LotNumber))
mrbAction.LotNumber = actions.First().LotNumber;
if (string.IsNullOrWhiteSpace(mrbAction.PartNumber))
mrbAction.PartNumber = actions.First().PartNumber;
}
}
if (customerNames is null || customerNames.Count() <= 0)
customerNames = await customerService.GetAllCustomerNames();
StateHasChanged();
}
private bool FormIsValid() {
bool actionIsValid = mrbAction.Action.Equals("Block") || mrbAction.Action.Equals("Convert") ||
mrbAction.Action.Equals("Other") || mrbAction.Action.Equals("Recall") || mrbAction.Action.Equals("Scrap") ||
mrbAction.Action.Equals("Unblock") || mrbAction.Action.Equals("Waiver");
actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Customer) &&
!string.IsNullOrWhiteSpace(mrbAction.PartNumber) &&
!string.IsNullOrWhiteSpace(mrbAction.LotNumber);
if (mrbAction.Action.Equals("Scrap"))
return actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Justification);
return actionIsValid;
}
private async void SaveMRBAction() {
processingSave = true;
try {
if (!FormIsValid()) throw new Exception("You must complete the form before saving!");
if (mrbAction.MRBNumber > 0) {
if (mrbAction.ActionID <= 0) {
await mrbService.CreateMRBAction(mrbAction);
snackbar.Add("MRB action created", Severity.Success);
} else {
await mrbService.UpdateMRBAction(mrbAction);
snackbar.Add("MRB action updated", Severity.Success);
}
actions = (await mrbService.GetMRBActionsForMRB(mrbAction.MRBNumber, true)).OrderByDescending(a => a.ActionID);
} else {
snackbar.Add("MRB action saved", Severity.Success);
}
StateHasChanged();
MudDialog.Close(DialogResult.Ok(mrbAction));
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
processingSave = false;
}
private async void DeleteMRBAction() {
processingDelete = true;
try {
if (mrbAction is null) throw new Exception("MRB action cannot be null!");
if (mrbAction.ActionID <= 0)
throw new Exception("You cannot delete an action before creating it!");
if (mrbAction.MRBNumber <= 0)
throw new Exception("Invalid MRB number!");
await mrbService.DeleteMRBAction(mrbAction);
snackbar.Add("MRB action successfully deleted", Severity.Success);
StateHasChanged();
MudDialog.Close(DialogResult.Ok<MRBAction>(null));
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
processingDelete = false;
}
private void Cancel() {
MudDialog.Cancel();
}
private async Task<IEnumerable<string>> PartNumberSearch(string value, CancellationToken token) {
if (actions is null) return new List<string> { value };
if (string.IsNullOrWhiteSpace(value)) return new string[0];
HashSet<string> partNumbers = new();
partNumbers.Add(value);
foreach (MRBAction action in actions) {
if (action.PartNumber.Contains(value, StringComparison.InvariantCultureIgnoreCase))
partNumbers.Add(action.PartNumber);
}
return partNumbers;
}
private async Task<IEnumerable<string>> LotNumberSearch(string value, CancellationToken token) {
if (actions is null) return new List<string> { value };
if (string.IsNullOrWhiteSpace(value)) return new string[0];
HashSet<string> lotNumbers = new();
lotNumbers.Add(value);
foreach (MRBAction action in actions) {
if (action.LotNumber.Contains(value, StringComparison.InvariantCultureIgnoreCase))
lotNumbers.Add(action.LotNumber);
}
return lotNumbers;
}
}

View File

@ -0,0 +1,111 @@
@inject IApprovalService approvalService
@inject ISnackbar snackbar
@inject MesaFabApprovalAuthStateProvider authStateProvider
<MudDialog>
<DialogContent>
@if (availableApprovers is not null) {
<MudPaper Class="p-2">
<MudSelect T="User"
Label="Select a User"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=selectedUser
Text="@(selectedUser is null ? "" : selectedUser.GetFullName())">
@foreach (User user in availableApprovers) {
<MudSelectItem Value="@user">
@user.GetFullName()
</MudSelectItem>
}
</MudSelect>
</MudPaper>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=Submit>
<MudText>Submit</MudText>
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
Class="m1-auto"
OnClick=Cancel>
<MudText>Cancel</MudText>
</MudButton>
</DialogActions>
</MudDialog>
<MudOverlay Visible=processing DarkBackground="true" AutoClose="false">
<MudProgressCircular Color="Color.Info" Size="Size.Medium" Indeterminate="true" />
</MudOverlay>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter]
public User selectedUser { get; set; }
private bool processing = false;
private HashSet<User> availableApprovers = new();
protected override async Task OnInitializedAsync() {
try {
processing = true;
string roleName = "QA_PRE_APPROVAL";
string subRoleName = "QA_PRE_APPROVAL";
IEnumerable<User> qaApprovers = await GetApprovalGroupMembersForRoleAndSubRole(roleName, subRoleName);
foreach (User approver in qaApprovers)
availableApprovers.Add(approver);
roleName = "MRB Approver";
subRoleName = "MRBApprover";
IEnumerable<User> mrbApprovers = await GetApprovalGroupMembersForRoleAndSubRole(roleName, subRoleName);
foreach (User approver in mrbApprovers)
availableApprovers.Add(approver);
selectedUser = availableApprovers.First();
processing = false;
} catch (Exception ex) {
processing = false;
snackbar.Add($"Unable to get all approvers, because {ex.Message}", Severity.Error);
}
}
private void Submit() {
MudDialog.Close(DialogResult.Ok(selectedUser));
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
private async Task<IEnumerable<User>> GetApprovalGroupMembersForRoleAndSubRole(string roleName, string subRoleName) {
HashSet<User> members = new();
int roleId = await approvalService.GetRoleIdForRoleName(roleName);
if (roleId <= 0) throw new Exception($"could not find {roleName} role ID");
IEnumerable<SubRole> subRoles = await approvalService.GetSubRolesForSubRoleName(subRoleName, roleId);
foreach (SubRole subRole in subRoles) {
IEnumerable<User> subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User member in subRoleMembers) {
members.Add(member);
}
}
return members;
}
}

View File

@ -0,0 +1,8 @@
@attribute [AllowAnonymous]
@inject NavigationManager Navigation
@code {
protected override void OnInitialized() {
Navigation.NavigateTo("login");
}
}

View File

@ -0,0 +1,74 @@
@inject IUserService userService
@inject ISnackbar snackbar
@inject MesaFabApprovalAuthStateProvider authStateProvider
<MudDialog>
<DialogContent>
@if (allUsers is not null) {
<MudPaper Class="p-2">
<MudSelect T="User"
Label="Select a User"
Required
Variant="Variant.Outlined"
AnchorOrigin="Origin.BottomCenter"
@bind-Value=selectedUser
Text="@(selectedUser is null ? "" : selectedUser.GetFullName())">
@foreach (User user in allUsers) {
<MudSelectItem Value="@user">
@user.GetFullName()
</MudSelectItem>
}
</MudSelect>
</MudPaper>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m1-auto"
OnClick=Submit>
<MudText>Submit</MudText>
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
Class="m1-auto"
OnClick=Cancel>
<MudText>Cancel</MudText>
</MudButton>
</DialogActions>
</MudDialog>
<MudOverlay Visible=processing DarkBackground="true" AutoClose="false">
<MudProgressCircular Color="Color.Info" Size="Size.Medium" Indeterminate="true" />
</MudOverlay>
@code {
[CascadingParameter] MudDialogInstance MudDialog { get; set; }
[Parameter]
public User selectedUser { get; set; }
private bool processing = false;
private IEnumerable<User> allUsers = new List<User>();
protected override async Task OnInitializedAsync() {
try {
processing = true;
selectedUser = authStateProvider.CurrentUser;
allUsers = await userService.GetAllActiveUsers();
processing = false;
} catch (Exception ex) {
processing = false;
snackbar.Add($"Unable to get all users, because {ex.Message}", Severity.Error);
}
}
private void Submit() {
MudDialog.Close(DialogResult.Ok(selectedUser));
}
private void Cancel() {
MudDialog.Close(DialogResult.Cancel());
}
}

View File

@ -0,0 +1,248 @@
@page "/"
@page "/Dashboard"
@inject IConfiguration Configuration
@inject MesaFabApprovalAuthStateProvider stateProvider
@inject IApprovalService approvalService
@inject IMemoryCache cache
@inject NavigationManager navigationManager
@inject ISnackbar snackbar
@inject IMRBService mrbService
@inject IECNService ecnService
@inject ICAService caService
@inject IJSRuntime jsRuntime
<PageTitle>Dashboard</PageTitle>
<MudPaper Class="p-2 m-2">
<MudText Typo="Typo.h3" Align="Align.Center">Dashboard</MudText>
</MudPaper>
<MudPaper Class="p-2 m-2">
<MudTabs Class="p-2" Rounded Centered Color="Color.Info" MinimumTabWidth="50%">
<MudTabPanel Text="My Active Approvals" Style="min-width:50%; text-align: center;">
@if (stateProvider.CurrentUser is not null && approvalList is not null && !myApprovalsProcessing) {
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-column justify-center">
<MudText Typo="Typo.h4" Align="Align.Center">My Active Approvals</MudText>
<MudDivider DividerType="DividerType.Middle" Class="my-2" />
<MudTable Items="@approvalList"
Class="m-2"
Striped
SortLabel="Sort by">
<HeaderContent>
<MudTh>
<MudTableSortLabel SortBy="new Func<Approval,object>(x=>x.IssueID)">
Issue ID
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<Approval,object>(x=>x.SubRoleCategoryItem)">
Role
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<Approval,object>(x=>x.AssignedDate)">
Assigned Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<Approval,object>(x=>x.Step)">
Step
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Issue ID">
@if (context.IssueID > 0) {
<MudLink OnClick="@(() => FollowLink(context.IssueID))">@context.IssueID</MudLink>
}
</MudTd>
<MudTd DataLabel="Role">@context.SubRoleCategoryItem</MudTd>
<MudTd DataLabel="Assigned Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.AssignedDate)</MudTd>
<MudTd DataLabel="Step">@context.Step</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
</MudPaper>
} else {
<MudOverlay Visible DarkBackground="true" AutoClose="false">
<MudText Align="Align.Center" Typo="Typo.h3">Processing</MudText>
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
}
</MudTabPanel>
<MudTabPanel Text="My MRBs" Style="min-width:50%; text-align: center;">
@if (stateProvider.CurrentUser is not null && myMRBs is not null && !myMrbsProcessing) {
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-column justify-center">
<MudText Typo="Typo.h4" Align="Align.Center">My MRBs</MudText>
<MudDivider DividerType="DividerType.Middle" Class="my-2" />
<MudTable Items="@myMRBs"
Class="m-2"
Striped
SortLabel="Sort by"
Filter="new Func<MRB, bool>(FilterFuncForMRBTable)">
<ToolBarContent>
<MudSpacer />
<MudTextField @bind-Value="mrbSearchString"
Placeholder="Search"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
Class="mt-0" />
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<MRB,object>(x=>x.MRBNumber)">
MRB#
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.Title)">
Title
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.SubmittedDate)">
Submitted Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.ApprovalDate)">
Approval Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.CancelDate)">
Cancel Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.CloseDate)">
Completed Date
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="MRB#">
<MudLink OnClick="@(() => GoTo($"mrb/{context.MRBNumber}"))">@context.MRBNumber</MudLink>
</MudTd>
<MudTd DataLabel="Title">@context.Title</MudTd>
<MudTd DataLabel="Submitted Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.SubmittedDate)</MudTd>
<MudTd DataLabel="Approval Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.ApprovalDate)</MudTd>
<MudTd DataLabel="Cancel Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CancelDate)</MudTd>
<MudTd DataLabel="Completed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CloseDate)</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
</MudPaper>
} else {
<MudOverlay Visible DarkBackground="true" AutoClose="false">
<MudText Align="Align.Center" Typo="Typo.h3">Processing</MudText>
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
}
</MudTabPanel>
</MudTabs>
</MudPaper>
@code {
private IEnumerable<Approval> approvalList = new List<Approval>();
private IEnumerable<MRB> myMRBs = new List<MRB>();
private IEnumerable<int> ecnNumbers = new HashSet<int>();
private IEnumerable<int> caNumbers = new HashSet<int>();
private IEnumerable<int> mrbNumbers = new HashSet<int>();
private bool myApprovalsProcessing = false;
private bool myMrbsProcessing = false;
private string mrbSearchString = "";
protected async override Task OnParametersSetAsync() {
try {
if (stateProvider.CurrentUser is not null) {
myApprovalsProcessing = true;
approvalList = (await approvalService.GetApprovalsForUserId(stateProvider.CurrentUser.UserID, true))
.Where(a => a.CompletedDate > DateTime.Now && a.ItemStatus == 0)
.ToList()
.OrderByDescending(x => x.AssignedDate);
myApprovalsProcessing = false;
myMrbsProcessing = true;
myMRBs = (await mrbService.GetAllMRBs(false)).Where(m => m.OriginatorID == stateProvider.CurrentUser.UserID)
.ToList()
.OrderByDescending(x => x.SubmittedDate);
myMrbsProcessing = false;
}
} catch (Exception ex) {
myMrbsProcessing = false;
snackbar.Add($"Unable to load the dashboard, because {ex.Message}", Severity.Error);
}
}
private async Task FollowLink(int issueId) {
HashSet<Task> tasks = new();
bool isEcn = false;
bool isCa = false;
bool isMrb = false;
if (ecnNumbers.Contains(issueId))
isEcn = true;
if (caNumbers.Contains(issueId))
isCa = true;
if (mrbNumbers.Contains(issueId))
isMrb = true;
if (!isEcn && !isCa && !isMrb) {
Task<bool> isEcnTask = ecnService.ECNNumberIsValid(issueId);
tasks.Add(isEcnTask);
Task<bool> isCaTask = caService.CANumberIsValid(issueId);
tasks.Add(isCaTask);
Task<bool> isMrbTask = mrbService.NumberIsValid(issueId);
tasks.Add(isMrbTask);
await Task.WhenAll(tasks);
if (isEcnTask.Result) {
isEcn = true;
} else if (isCaTask.Result) {
isCa = true;
} else if (isMrbTask.Result) {
isMrb = true;
}
}
if (isEcn) await GoToExternal($"{Configuration["OldFabApprovalUrl"]}/ECN/Edit?IssueID={issueId}", "");
if (isCa) await GoToExternal($"{Configuration["OldFabApprovalUrl"]}/CorrectiveAction/Edit?IssueID={issueId}", "");
if (isMrb) GoTo($"mrb/{issueId}");
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo("/" + page);
}
private async Task GoToExternal(string url, string content) {
IJSObjectReference windowModule = await jsRuntime.InvokeAsync<IJSObjectReference>("import", "./js/OpenInNewWindow.js");
await windowModule.InvokeAsync<object>("OpenInNewWindow", url, content);
}
private bool FilterFuncForMRBTable(MRB mrb) => MRBFilterFunc(mrb, mrbSearchString);
private bool MRBFilterFunc(MRB mrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (mrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.MRBNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
}

View File

@ -0,0 +1,113 @@
@page "/login"
@page "/login/{redirectUrl}"
@page "/login/{redirectUrl}/{redirectUrlSub}"
@attribute [AllowAnonymous]
@inject MesaFabApprovalAuthStateProvider authStateProvider
@inject NavigationManager navManager
@inject ISnackbar snackbar
<MudPaper Class="p-2 m-2">
<MudText Typo="Typo.h3" Align="Align.Center">Login</MudText>
</MudPaper>
<MudPaper Class="p-2 m-2">
<MudForm @bind-IsValid="@success" @bind-Errors="@errors">
<MudTextField T="string"
Label="Windows Username"
Required="true"
RequiredError="Username is required!"
Variant="Variant.Outlined"
@bind-Value=username
Class="m-1"
Immediate="true"
AutoFocus
OnKeyDown=SubmitIfEnter />
<MudTextField T="string"
Label="Windows Password"
Required="true"
RequiredError="Password is required!"
Variant="Variant.Outlined"
@bind-Value=password
InputType="InputType.Password"
Class="m-1"
Immediate="true"
OnKeyDown=SubmitIfEnter />
<MudButton
Variant="Variant.Filled"
Color="Color.Tertiary"
Disabled="@(!success)"
Class="m-1"
OnClick=SubmitLogin >
@if (processing) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Log In</MudText>
}
</MudButton>
<MudDivider />
@* <MudButton
Variant="Variant.Filled"
Color="Color.Tertiary"
Class="m-1"
OnClick="LoginLocal" >
@if (processingLocal) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Log In (SSO)</MudText>
}
</MudButton> *@
</MudForm>
</MudPaper>
@code {
[Parameter]
public string? redirectUrl { get; set; }
[Parameter]
public string? redirectUrlSub { get; set; }
private bool success;
private bool processing = false;
private bool processingLocal = false;
private string[] errors = { };
private string? username;
private string? password;
private async Task SubmitLogin() {
processing = true;
if (string.IsNullOrWhiteSpace(username)) snackbar.Add("Username is required!", Severity.Error);
else if (string.IsNullOrWhiteSpace(password)) snackbar.Add("Password is required!", Severity.Error);
else {
await authStateProvider.LoginAsync(username, password);
if (!string.IsNullOrWhiteSpace(redirectUrl) && !string.IsNullOrWhiteSpace(redirectUrlSub)) {
navManager.NavigateTo($"{redirectUrl}/{redirectUrlSub}");
} else if (!string.IsNullOrWhiteSpace(redirectUrl)) {
navManager.NavigateTo(redirectUrl);
} else {
navManager.NavigateTo("dashboard");
}
}
processing = false;
}
private async Task SubmitIfEnter(KeyboardEventArgs e) {
if (e.Key == "Enter" && success) {
SubmitLogin();
}
}
private async Task LoginLocal() {
processingLocal = true;
await authStateProvider.LoginLocal();
if (!string.IsNullOrWhiteSpace(redirectUrl) && !string.IsNullOrWhiteSpace(redirectUrlSub)) {
navManager.NavigateTo($"{redirectUrl}/{redirectUrlSub}");
} else if (!string.IsNullOrWhiteSpace(redirectUrl)) {
navManager.NavigateTo(redirectUrl);
} else {
navManager.NavigateTo("dashboard");
}
processingLocal = false;
}
}

View File

@ -0,0 +1,119 @@
@page "/mrb/all"
@using System.Globalization
@inject IMRBService mrbService
@inject ISnackbar snackbar
@inject IMemoryCache cache
@inject NavigationManager navigationManager
<PageTitle>MRB</PageTitle>
<MudPaper Class="p-2 m-2">
<MudText Typo="Typo.h3" Align="Align.Center">MRB List</MudText>
</MudPaper>
@if (allMrbs is not null && allMrbs.Count() > 0) {
<MudTable Items="@allMrbs"
Class="m-2"
Striped="true"
Filter="new Func<MRB,bool>(FilterFuncForTable)"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
<MudSpacer />
<MudTextField @bind-Value="searchString"
Placeholder="Search"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
Class="mt-0" />
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<MRB,object>(x=>x.MRBNumber)">
MRB#
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.Title)">
Title
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.OriginatorName)">
Originator
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.SubmittedDate)">
Submitted Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.ApprovalDate)">
Approval Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<MRB,object>(x=>x.CloseDate)">
Closed Date
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="MRB#">
<MudLink OnClick="@(() => GoTo($"mrb/{context.MRBNumber}"))">@context.MRBNumber</MudLink>
</MudTd>
<MudTd DataLabel="Title">@context.Title</MudTd>
<MudTd DataLabel="Originator">@context.OriginatorName</MudTd>
<MudTd DataLabel="Submitted Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.SubmittedDate)</MudTd>
<MudTd DataLabel="Approval Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.ApprovalDate)</MudTd>
<MudTd DataLabel="Closed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.CloseDate)</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
}
<MudOverlay @bind-Visible=inProcess DarkBackground="true" AutoClose="false">
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
@code {
private bool inProcess = false;
private string searchString = "";
private IEnumerable<MRB> allMrbs = new List<MRB>();
protected override async Task OnParametersSetAsync() {
inProcess = true;
try {
if (mrbService is null) {
throw new Exception("MRB service not injected!");
} else {
allMrbs = await mrbService.GetAllMRBs(false);
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
inProcess = false;
}
private bool FilterFuncForTable(MRB mrb) => FilterFunc(mrb, searchString);
private bool FilterFunc(MRB mrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (mrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.OriginatorName.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (mrb.MRBNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,119 @@
@page "/pcrb/all"
@using System.Globalization
@inject IPCRBService pcrbService
@inject ISnackbar snackbar
@inject IMemoryCache cache
@inject NavigationManager navigationManager
<PageTitle>PCRB</PageTitle>
<MudPaper Class="p-2 m-2">
<MudText Typo="Typo.h3" Align="Align.Center">PCRB List</MudText>
</MudPaper>
@if (allPCRBs is not null && allPCRBs.Count() > 0) {
<MudTable Items="@allPCRBs"
Class="m-2"
Striped="true"
Filter="new Func<PCRB,bool>(FilterFuncForTable)"
SortLabel="Sort By"
Hover="true">
<ToolBarContent>
<MudSpacer />
<MudTextField @bind-Value="searchString"
Placeholder="Search"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
IconSize="Size.Medium"
Class="mt-0" />
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel InitialDirection="SortDirection.Descending" SortBy="new Func<PCRB,object>(x=>x.PlanNumber)">
Plan#
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.Title)">
Title
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.OwnerName)">
Owner
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.InsertTimeStamp)">
Submitted Date
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.LastUpdateDate)">
Last Updated
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="new Func<PCRB,object>(x=>x.ClosedDate)">
Closed Date
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Plan#">
<MudLink OnClick="@(() => GoTo($"pcrb/{context.PlanNumber.ToString()}"))">@context.PlanNumber</MudLink>
</MudTd>
<MudTd DataLabel="Title">@context.Title</MudTd>
<MudTd DataLabel="Owner">@context.OwnerName</MudTd>
<MudTd DataLabel="Submitted Date">@DateTimeUtilities.GetDateAsStringMinDefault(context.InsertTimeStamp)</MudTd>
<MudTd DataLabel="Last Updated">@DateTimeUtilities.GetDateAsStringMinDefault(context.LastUpdateDate)</MudTd>
<MudTd DataLabel="Closed Date">@DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate)</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
}
<MudOverlay @bind-Visible=inProcess DarkBackground="true" AutoClose="false">
<MudProgressCircular Color="Color.Info" Size="Size.Large" Indeterminate="true" />
</MudOverlay>
@code {
private bool inProcess = false;
private string searchString = "";
private IEnumerable<PCRB> allPCRBs = new List<PCRB>();
protected override async Task OnParametersSetAsync() {
inProcess = true;
try {
if (pcrbService is null) {
throw new Exception("PCRB service not injected!");
} else {
allPCRBs = await pcrbService.GetAllPCRBs(false);
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
inProcess = false;
}
private bool FilterFuncForTable(PCRB pcrb) => FilterFunc(pcrb, searchString);
private bool FilterFunc(PCRB pcrb, string searchString) {
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (pcrb.Title.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.OwnerName.ToLower().Contains(searchString.Trim().ToLower()))
return true;
if (pcrb.PlanNumber.ToString().Contains(searchString.Trim()))
return true;
return false;
}
private void GoTo(string page) {
cache.Set("redirectUrl", page);
navigationManager.NavigateTo(page);
}
}

View File

@ -0,0 +1,263 @@
@page "/pcrb/{planNumber}"
@page "/pcrb/new"
@using System.Text
@inject ISnackbar snackbar
@inject IPCRBService pcrbService
@inject IUserService userService
@inject IMemoryCache cache
@inject NavigationManager navigationManager
@inject MesaFabApprovalAuthStateProvider authStateProvider
<PageTitle>PCRB @planNumber</PageTitle>
<MudPaper Class="p-2 m-2 d-flex flex-row justify-content-between">
<MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
Variant="Variant.Outlined"
Color="Color.Dark"
OnClick="@ReturnToAllPcrbs"
Size="Size.Large" />
<MudText Typo="Typo.h3" Align="Align.Center">PCRB @planNumber</MudText>
<MudPaper Height="100%" Width="0.1%" Square="true" />
</MudPaper>
@if (pcrb is not null) {
<MudTimeline Class="mt-2 pt-2" TimelineOrientation="TimelineOrientation.Horizontal"
TimelinePosition="TimelinePosition.Bottom">
@for (int i = 0; i < PCRB.Stages.Length; i++) {
Color color;
if (pcrb.CurrentStep > i || pcrb.CurrentStep == (PCRB.Stages.Length - 1)) {
color = Color.Success;
} else if (pcrb.CurrentStep == i) {
color = Color.Info;
} else {
color = Color.Dark;
}
string stageName = PCRB.Stages[i];
<MudTimelineItem Color="@color" Variant="Variant.Filled">
<MudText Align="Align.Center" Color="@color">@stageName</MudText>
</MudTimelineItem>
}
</MudTimeline>
bool pcrbIsSubmitted = pcrb.InsertTimeStamp > DateTimeUtilities.MIN_DT;
bool userIsOriginator = pcrb.OwnerID == authStateProvider.CurrentUser?.UserID;
bool userIsAdmin = authStateProvider.CurrentUser is null ? false : authStateProvider.CurrentUser.IsAdmin;
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-center"
Elevation="10">
@if (!pcrbIsSubmitted && !string.IsNullOrWhiteSpace(pcrb.Title)) {
<MudButton Variant="Variant.Filled"
Color="Color.Tertiary"
OnClick=SavePCRB>
@if (saveInProcess) {
<MudProgressCircular Class="m-1" Size="Size.Small" Indeterminate="true" />
<MudText>Processing</MudText>
} else {
<MudText>Save</MudText>
}
</MudButton>
}
</MudPaper>
@if (!pcrbIsSubmitted && GetIncompleteFields().Count() > 0) {
IEnumerable<string> incompleteFields = GetIncompleteFields();
StringBuilder errorBuilder = new();
errorBuilder.Append($"Incomplete fields: {incompleteFields.First()}");
for (int i = 1; i < incompleteFields.Count(); i++) {
errorBuilder.Append($", {incompleteFields.ElementAt(i)}");
}
<MudPaper Outlined Class="p-2 m-2">
<MudText Align="Align.Center" Color="Color.Secondary" Typo="Typo.h6">
@errorBuilder.ToString()
</MudText>
</MudPaper>
}
<MudPaper Outlined="true"
Class="p-2 m-2 d-flex flex-wrap gap-3 justify-content-center align-content-start"
Elevation="10">
<MudTextField @bind-Value=pcrb.PlanNumber
Text="@pcrb.PlanNumber.ToString()"
T="int"
Disabled="true"
Label="Plan#"
Required
Variant="Variant.Outlined" />
<MudTextField @bind-Value=pcrb.Title
Text="@pcrb.Title"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
AutoFocus
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Title" />
<MudSelect T="User"
Label="Originator"
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
ToStringFunc="@UserToNameConverter"
Disabled=@(pcrbIsSubmitted)
@bind-Value=@selectedOwner>
@foreach (User user in allActiveUsers.OrderBy(u => u.LastName)) {
<MudSelectItem T="User" Value="@(user)" />
}
</MudSelect>
<MudSelect T="string"
Variant="Variant.Outlined"
Required
Clearable
AnchorOrigin="Origin.BottomCenter"
Disabled="@pcrbIsSubmitted"
@bind-Value="@pcrb.ChangeLevel"
Text="@pcrb.ChangeLevel"
Label="Change Level">
<MudSelectItem Value="@("Global")" />
<MudSelectItem Value="@("Other Site + Mesa")" />
<MudSelectItem Value="@("Mesa")" />
</MudSelect>
<MudCheckBox Disabled="@pcrbIsSubmitted"
Color="Color.Tertiary"
@bind-Value=pcrb.IsITAR
Label="Export Controlled"
LabelPosition="LabelPosition.Start" />
<MudTextField Disabled="true"
T="string"
Value="@DateTimeUtilities.GetDateAsStringMinDefault(pcrb.InsertTimeStamp)"
Label="Submit Date"
Variant="Variant.Outlined" />
<MudTextField Disabled="true"
T="string"
Value="@DateTimeUtilities.GetDateAsStringMinDefault(pcrb.LastUpdateDate)"
Label="Last Update"
Variant="Variant.Outlined" />
<MudTextField @bind-Value=pcrb.ChangeDescription
Text="@pcrb.ChangeDescription"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Description" />
<MudTextField @bind-Value=pcrb.ReasonForChange
Text="@pcrb.ReasonForChange"
Disabled="@(pcrbIsSubmitted)"
T="string"
AutoGrow
Immediate
Clearable
Required
Variant="Variant.Outlined"
Label="Reason For Change" />
</MudPaper>
}
@code {
[Parameter]
public string planNumber { get; set; } = "";
private int planNumberInt = 0;
private PCRB pcrb = null;
private IEnumerable<User> allActiveUsers = new List<User>();
private User selectedOwner = null;
private bool processing = false;
private bool saveInProcess = false;
protected override async Task OnParametersSetAsync() {
processing = true;
try {
allActiveUsers = await userService.GetAllActiveUsers();
if (!string.IsNullOrWhiteSpace(planNumber) && Int32.TryParse(planNumber, out planNumberInt)) {
pcrb = await pcrbService.GetPCRBByPlanNumber(planNumberInt, false);
if (pcrb.OwnerID > 0) selectedOwner = await userService.GetUserByUserId(pcrb.OwnerID);
} else {
int ownerID = 0;
string ownerName = string.Empty;
if (authStateProvider.CurrentUser is not null) {
selectedOwner = authStateProvider.CurrentUser;
ownerID = authStateProvider.CurrentUser.UserID;
ownerName = authStateProvider.CurrentUser.GetFullName();
}
pcrb = new() {
OwnerID = ownerID,
OwnerName = ownerName,
CurrentStep = 0
};
}
} catch (Exception ex) {
snackbar.Add(ex.Message, Severity.Error);
}
processing = false;
}
private Func<User, string> UserToNameConverter = u => u is null ? string.Empty : u.GetFullName();
private void ReturnToAllPcrbs() {
cache.Set("redirectUrl", $"pcrb/all");
navigationManager.NavigateTo("pcrb/all");
}
private IEnumerable<string> GetIncompleteFields() {
List<string> incompleteFields = new();
if (string.IsNullOrWhiteSpace(pcrb.Title))
incompleteFields.Add("Title");
if (selectedOwner is null || pcrb.OwnerID <= 0 || string.IsNullOrWhiteSpace(pcrb.OwnerName))
incompleteFields.Add("Originator");
if (string.IsNullOrWhiteSpace(pcrb.ChangeLevel))
incompleteFields.Add("Change Level");
if (string.IsNullOrWhiteSpace(pcrb.ChangeDescription))
incompleteFields.Add("Description");
if (string.IsNullOrWhiteSpace(pcrb.ReasonForChange))
incompleteFields.Add("Reason For Change");
return incompleteFields;
}
private async void SavePCRB() {
saveInProcess = true;
try {
if (pcrb is null) throw new Exception("PCRB cannot be null");
int initialPlanNumber = pcrb.PlanNumber;
pcrb.OwnerID = selectedOwner.UserID;
pcrb.OwnerName = selectedOwner.GetFullName();
if (initialPlanNumber <= 0) {
await pcrbService.CreateNewPCRB(pcrb);
} else {
await pcrbService.UpdatePCRB(pcrb);
}
pcrb = await pcrbService.GetPCRBByTitle(pcrb.Title, true);
cache.Set("redirectUrl", $"pcrb/{pcrb.PlanNumber}");
saveInProcess = false;
StateHasChanged();
snackbar.Add($"PCRB {pcrb.PlanNumber} successfully saved", Severity.Success);
if (initialPlanNumber <= 0)
navigationManager.NavigateTo($"pcrb/{pcrb.PlanNumber}");
} catch (Exception ex) {
saveInProcess = false;
snackbar.Add($"Unable to save PCRB, because {ex.Message}", Severity.Error);
}
}
}

View File

@ -0,0 +1,64 @@
using MesaFabApproval.Client;
using MesaFabApproval.Client.Utilities;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor.Services;
using MesaFabApproval.Client.Services;
using Microsoft.AspNetCore.Components.Authorization;
using System.Net.Http.Headers;
using MudBlazor;
WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args);
string _apiBaseUrl = builder.Configuration["FabApprovalApiBaseUrl"] ??
throw new NullReferenceException("FabApprovalApiBaseUrl not found in config");
builder.Services.AddTransient<ApiHttpClientHandler>();
builder.Services
.AddHttpClient("API_Handler", client => {
client.BaseAddress = new Uri(_apiBaseUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
});
builder.Services
.AddHttpClient("API", client => {
client.BaseAddress = new Uri(_apiBaseUrl);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
})
.AddHttpMessageHandler<ApiHttpClientHandler>();
builder.Services.AddMemoryCache();
builder.Services.AddMudServices(config => {
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomCenter;
config.SnackbarConfiguration.PreventDuplicates = true;
config.SnackbarConfiguration.MaxDisplayedSnackbars = 5;
config.SnackbarConfiguration.SnackbarVariant = Variant.Filled;
config.SnackbarConfiguration.ShowCloseIcon = true;
config.SnackbarConfiguration.VisibleStateDuration = 7000;
config.SnackbarConfiguration.HideTransitionDuration = 500;
config.SnackbarConfiguration.ShowTransitionDuration = 500;
});
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<ICustomerService, CustomerService>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IECNService, ECNService>();
builder.Services.AddScoped<ICAService, CAService>();
builder.Services.AddScoped<IPCRBService, PCRBService>();
builder.Services.AddScoped<IMRBService, MRBService>();
builder.Services.AddScoped<IApprovalService, ApprovalService>();
builder.Services.AddScoped<MesaFabApprovalAuthStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(sp =>
sp.GetRequiredService<MesaFabApprovalAuthStateProvider>());
builder.Services.AddAuthorizationCore();
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
await builder.Build().RunAsync();

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net8.0\browser-wasm\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
<ProjectGuid>34d52f44-a81f-4247-8180-16e204824a07</ProjectGuid>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,300 @@
using System.Text;
using System.Text.Json;
using MesaFabApproval.Shared.Models;
using Microsoft.Extensions.Caching.Memory;
namespace MesaFabApproval.Client.Services;
public interface IApprovalService {
Task<int> GetRoleIdForRoleName(string roleName);
Task<IEnumerable<SubRole>> GetSubRolesForSubRoleName(string subRoleName, int roleId);
Task<IEnumerable<User>> GetApprovalGroupMembers(int subRoleId);
Task CreateApproval(Approval approval);
Task UpdateApproval(Approval approval);
Task Approve(Approval approval);
Task Deny(Approval approval);
Task<IEnumerable<Approval>> GetApprovalsForIssueId(int issueId, bool bypassCache);
Task<IEnumerable<Approval>> GetApprovalsForUserId(int userId, bool bypassCache);
}
public class ApprovalService : IApprovalService {
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
public ApprovalService(IMemoryCache cache, IHttpClientFactory httpClientFactory) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ??
throw new ArgumentNullException("IHttpClientFactory not injected");
}
public async Task CreateApproval(Approval approval) {
if (approval is null) throw new ArgumentNullException("approval cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, "approval");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(approval),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to create approval, because {responseMessage.ReasonPhrase}");
}
await GetApprovalsForIssueId(approval.IssueID, true);
await GetApprovalsForUserId(approval.UserID, true);
}
public async Task<IEnumerable<Approval>> GetApprovalsForIssueId(int issueId, bool bypassCache) {
if (issueId <= 0) throw new ArgumentException($"{issueId} is not a valid issue ID");
IEnumerable<Approval>? approvals = null;
if (!bypassCache)
approvals = _cache.Get<IEnumerable<Approval>>($"approvals{issueId}");
if (approvals is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"approval/issue?issueId={issueId}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
approvals = JsonSerializer.Deserialize<IEnumerable<Approval>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse approvals from API response");
_cache.Set($"approvals{issueId}", approvals, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception($"Unable to get approvals, because {responseMessage.ReasonPhrase}");
}
}
foreach (Approval approval in approvals) {
if (approval.ItemStatus < 0)
approval.StatusMessage = "Denied";
if (approval.ItemStatus == 0)
approval.StatusMessage = "Assigned";
if (approval.ItemStatus > 0)
approval.StatusMessage = "Approved";
}
return approvals;
}
public async Task<IEnumerable<Approval>> GetApprovalsForUserId(int userId, bool bypassCache) {
if (userId <= 0) throw new ArgumentException($"{userId} is not a valid user ID");
IEnumerable<Approval>? approvals = null;
if (!bypassCache) approvals = _cache.Get<IEnumerable<Approval>>($"approvals{userId}");
if (approvals is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"approval/user?userId={userId}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
approvals = JsonSerializer.Deserialize<IEnumerable<Approval>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse approvals from API response");
_cache.Set($"approvals{userId}", approvals, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception($"Unable to get approvals, because {responseMessage.ReasonPhrase}");
}
}
foreach (Approval approval in approvals) {
if (approval.ItemStatus < 0)
approval.StatusMessage = "Denied";
if (approval.ItemStatus == 0)
approval.StatusMessage = "Assigned";
if (approval.ItemStatus > 0)
approval.StatusMessage = "Approved";
}
return approvals;
}
public async Task UpdateApproval(Approval approval) {
if (approval is null) throw new ArgumentNullException("approval cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, "approval");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(approval),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to update approval, because {responseMessage.ReasonPhrase}");
}
await GetApprovalsForIssueId(approval.IssueID, true);
await GetApprovalsForUserId(approval.UserID, true);
}
public async Task Approve(Approval approval) {
if (approval is null) throw new ArgumentNullException("approval cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, "approval/approve");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(approval),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Approval failed, because {responseMessage.ReasonPhrase}");
}
await GetApprovalsForIssueId(approval.IssueID, true);
await GetApprovalsForUserId(approval.UserID, true);
}
public async Task Deny(Approval approval) {
if (approval is null) throw new ArgumentNullException("approval cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, "approval/deny");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(approval),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Denial failed, because {responseMessage.ReasonPhrase}");
}
}
public async Task<int> GetRoleIdForRoleName(string roleName) {
if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("role name cannot be null or empty");
int roleId = _cache.Get<int>($"roleId{roleName}");
if (roleId <= 0) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"approval/roleId?roleName={roleName}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
roleId = JsonSerializer.Deserialize<int>(responseContent, jsonSerializerOptions);
if (roleId <= 0)
throw new Exception($"unable to find role ID for {roleName}");
_cache.Set($"roleId{roleName}", roleId, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception($"Unable to get role ID, because {responseMessage.ReasonPhrase}");
}
}
return roleId;
}
public async Task<IEnumerable<SubRole>> GetSubRolesForSubRoleName(string subRoleName, int roleId) {
if (string.IsNullOrWhiteSpace(subRoleName)) throw new ArgumentException("role name cannot be null or empty");
if (roleId <= 0) throw new ArgumentException($"{roleId} is not a valid role ID");
IEnumerable<SubRole>? subRoles = _cache.Get<IEnumerable<SubRole>>($"subRoles{subRoleName}");
if (subRoles is null || subRoles.Count() <= 0) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"approval/subRoles?subRoleName={subRoleName}&roleId={roleId}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
subRoles = JsonSerializer.Deserialize<IEnumerable<SubRole>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse sub roles from API response");
if (subRoles is not null && subRoles.Count() > 0) {
_cache.Set($"subRoles{subRoleName}", subRoles, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception($"unable to find sub roles for {subRoleName}");
}
} else {
throw new Exception($"Unable to get sub roles, because {responseMessage.ReasonPhrase}");
}
}
return subRoles;
}
public async Task<IEnumerable<User>> GetApprovalGroupMembers(int subRoleId) {
if (subRoleId <= 0) throw new ArgumentException($"{subRoleId} is not a valid sub role ID");
IEnumerable<User>? members = _cache.Get<IEnumerable<User>>($"approvalMembers{subRoleId}");
if (members is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"approval/members?subRoleId={subRoleId}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
members = JsonSerializer.Deserialize<IEnumerable<User>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse users from API response");
if (members is null || members.Count() <= 0)
throw new Exception($"unable to find group members for sub role {subRoleId}");
_cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception($"Unable to get group members, because {responseMessage.ReasonPhrase}");
}
}
return members;
}
}

View File

@ -0,0 +1,232 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Text.Json;
using MesaFabApproval.Shared.Models;
using Microsoft.Extensions.Caching.Memory;
namespace MesaFabApproval.Client.Services;
public interface IAuthenticationService {
Task<ClaimsPrincipal> SendAuthenticationRequest(string loginId, string password);
Task<ClaimsPrincipal> AttemptLocalUserAuth();
Task<ClaimsPrincipal> FetchAuthState();
Task ClearTokens();
Task ClearCurrentUser();
Task SetTokens(string jwt, string refreshToken);
Task SetLoginId(string loginId);
Task SetCurrentUser(User user);
Task<User> GetCurrentUser();
Task<AuthTokens> GetAuthTokens();
Task<string> GetLoginId();
ClaimsPrincipal GetClaimsPrincipalFromJwt(string jwt);
}
public class AuthenticationService : IAuthenticationService {
private readonly ILocalStorageService _localStorageService;
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
public AuthenticationService(ILocalStorageService localStorageService,
IMemoryCache cache,
IHttpClientFactory httpClientFactory) {
_localStorageService = localStorageService ??
throw new ArgumentNullException("ILocalStorageService not injected");
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected");
}
public async Task<ClaimsPrincipal> SendAuthenticationRequest(string loginId, string password) {
if (string.IsNullOrWhiteSpace(loginId)) throw new ArgumentException("loginId cannot be null or empty");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("password cannot be null or empty");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
AuthAttempt authAttempt = new() {
LoginID = loginId,
Password = password
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/login");
request.Content = new StringContent(JsonSerializer.Serialize(authAttempt),
Encoding.UTF8,
"application/json");
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode) {
string responseContent = await httpResponseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
LoginResult loginResult = JsonSerializer.Deserialize<LoginResult>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse login result from API response");
if (!loginResult.IsAuthenticated) throw new Exception($"User with Login ID {loginId} not authorized");
await SetLoginId(loginId);
await SetTokens(loginResult.AuthTokens.JwtToken, loginResult.AuthTokens.RefreshToken);
await SetCurrentUser(loginResult.User);
ClaimsPrincipal principal = GetClaimsPrincipalFromJwt(loginResult.AuthTokens.JwtToken);
return principal;
} else {
throw new Exception($"Login API request failed for {loginId}, because {httpResponseMessage.ReasonPhrase}");
}
}
public async Task<ClaimsPrincipal> AttemptLocalUserAuth() {
WindowsIdentity? user = WindowsIdentity.GetCurrent();
if (user is null)
throw new Exception("no authenticated user found");
if (!user.IsAuthenticated)
throw new Exception("you are not authenticated");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/login/localWindows");
request.Content = new StringContent(JsonSerializer.Serialize(user),
Encoding.UTF8,
"application/json");
HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request);
if (httpResponseMessage.IsSuccessStatusCode) {
string responseContent = await httpResponseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
LoginResult loginResult = JsonSerializer.Deserialize<LoginResult>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse login result from API response");
if (!loginResult.IsAuthenticated) throw new Exception($"User with Login ID {user.Name} not authorized");
await SetLoginId(loginResult.User.LoginID);
await SetTokens(loginResult.AuthTokens.JwtToken, loginResult.AuthTokens.RefreshToken);
await SetCurrentUser(loginResult.User);
ClaimsPrincipal principal = GetClaimsPrincipalFromJwt(loginResult.AuthTokens.JwtToken);
return principal;
} else {
throw new Exception($"Login API request failed for {user.Name}, because {httpResponseMessage.ReasonPhrase}");
}
}
public async Task<ClaimsPrincipal> FetchAuthState() {
string? jwt = _cache.Get<string>("MesaFabApprovalJwt");
if (jwt is null) jwt = await _localStorageService.GetItem("MesaFabApprovalJwt");
if (jwt is null) throw new Exception("Unable to find JWT");
ClaimsPrincipal principal = GetClaimsPrincipalFromJwt(jwt);
return principal;
}
public async Task ClearTokens() {
_cache.Remove("MesaFabApprovalJwt");
await _localStorageService.RemoveItem("MesaFabApprovalJwt");
_cache.Remove("MesaFabApprovalRefreshToken");
await _localStorageService.RemoveItem("MesaFabApprovalRefreshToken");
}
public async Task SetTokens(string jwt, string refreshToken) {
if (string.IsNullOrWhiteSpace(jwt)) throw new ArgumentNullException("JWT cannot be null or empty");
if (string.IsNullOrWhiteSpace(refreshToken)) throw new ArgumentNullException("Refresh token cannot be null or empty");
_cache.Set<string>("MesaFabApprovalJwt", jwt);
await _localStorageService.AddItem("MesaFabApprovalJwt", jwt);
_cache.Set<string>("MesaFabApprovalRefreshToken", refreshToken);
await _localStorageService.AddItem("MesaFabApprovalRefreshToken", refreshToken);
}
public async Task SetLoginId(string loginId) {
if (string.IsNullOrWhiteSpace(loginId)) throw new ArgumentNullException("LoginId cannot be null or empty");
_cache.Set<string>("MesaFabApprovalUserId", loginId);
await _localStorageService.AddItem("MesaFabApprovalUserId", loginId);
}
public async Task SetCurrentUser(User user) {
if (user is null) throw new ArgumentNullException("User cannot be null");
_cache.Set<User>("MesaFabApprovalCurrentUser", user);
await _localStorageService.AddItem<User>("MesaFabApprovalCurrentUser", user);
}
public async Task ClearCurrentUser() {
_cache.Remove("MesaFabApprovalCurrentUser");
await _localStorageService.RemoveItem("MesaFabApprovalCurrentUser");
_cache.Remove("MesaFabApprovalUserId");
await _localStorageService.RemoveItem("MesaFabApprovalUserId");
}
public async Task<User> GetCurrentUser() {
User? currentUser = null;
currentUser = _cache.Get<User>("MesaFabApprovalCurrentUser") ??
await _localStorageService.GetItem<User>("MesaFabApprovalCurrentUser");
return currentUser;
}
public async Task<AuthTokens> GetAuthTokens() {
AuthTokens? authTokens = null;
string? jwt = _cache.Get<string>("MesaFabApprovalJwt");
if (jwt is null) jwt = await _localStorageService.GetItem("MesaFabApprovalJwt");
string? refreshToken = _cache.Get<string>("MesaFabApprovalRefreshToken");
if (refreshToken is null) refreshToken = await _localStorageService.GetItem("MesaFabApprovalRefreshToken");
if (!string.IsNullOrWhiteSpace(jwt) && !string.IsNullOrWhiteSpace(refreshToken)) {
authTokens = new() {
JwtToken = jwt,
RefreshToken = refreshToken
};
}
return authTokens;
}
public async Task<string> GetLoginId() {
string? loginId = _cache.Get<string>("MesaFabApprovalUserId");
if (loginId is null) loginId = await _localStorageService.GetItem("MesaFabApprovalUserId");
return loginId;
}
public ClaimsPrincipal GetClaimsPrincipalFromJwt(string jwt) {
if (string.IsNullOrWhiteSpace(jwt)) throw new ArgumentException("JWT cannot be null or empty");
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
if (!tokenHandler.CanReadToken(jwt)) {
throw new Exception("Unable to parse JWT from API");
}
JwtSecurityToken jwtSecurityToken = tokenHandler.ReadJwtToken(jwt);
ClaimsIdentity identity = new ClaimsIdentity(jwtSecurityToken.Claims, "MesaFabApprovalWasm");
return new(identity);
}
}

View File

@ -0,0 +1,44 @@
using System.Text.Json;
namespace MesaFabApproval.Client.Services;
public interface ICAService {
Task<bool> CANumberIsValid(int number);
}
public class CAService : ICAService {
private readonly IHttpClientFactory _httpClientFactory;
public CAService(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory ??
throw new ArgumentNullException("IHttpClientFactory not injected");
}
public async Task<bool> CANumberIsValid(int number) {
if (number <= 0) return false;
try {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"ca/isValidCANumber?number={number}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
bool isValid = JsonSerializer.Deserialize<bool>(responseContent, jsonSerializerOptions);
return isValid;
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
} catch (Exception ex) {
throw new Exception($"Unable to determine if {number} is a valid CA#, because {ex.Message}");
}
}
}

View File

@ -0,0 +1,51 @@
using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
namespace MesaFabApproval.Client.Services;
public interface ICustomerService {
Task<IEnumerable<string>> GetAllCustomerNames();
}
public class CustomerService : ICustomerService {
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
public CustomerService(IMemoryCache cache, IHttpClientFactory httpClientFactory) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ??
throw new ArgumentNullException("IHttpClientFactory not injected");
}
public async Task<IEnumerable<string>> GetAllCustomerNames() {
IEnumerable<string>? allCustomerNames = null;
allCustomerNames = _cache.Get<IEnumerable<string>>("allCustomerNames");
if (allCustomerNames is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"customer/all");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
allCustomerNames = JsonSerializer.Deserialize<IEnumerable<string>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse names from API response");
_cache.Set($"allCustomerNames", allCustomerNames, DateTimeOffset.Now.AddHours(1));
} else {
throw new Exception($"Unable to get all customer names, because {responseMessage.ReasonPhrase}");
}
}
return allCustomerNames;
}
}

View File

@ -0,0 +1,52 @@
using System.Text.Json;
namespace MesaFabApproval.Client.Services;
public interface IECNService {
Task<string> ECNNumberIsValidStr(int ecnNumber);
Task<bool> ECNNumberIsValid(int number);
}
public class ECNService : IECNService {
private readonly IHttpClientFactory _httpClientFactory;
public ECNService(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory ??
throw new ArgumentNullException("IHttpClientFactory not injected");
}
public async Task<string> ECNNumberIsValidStr(int ecnNumber) {
if (ecnNumber <= 0 || !await ECNNumberIsValid(ecnNumber))
return $"{ecnNumber} is not a valid ECN#";
return null;
}
public async Task<bool> ECNNumberIsValid(int number) {
if (number <= 0) return false;
try {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"ecn/isValidEcnNumber?number={number}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
bool isValid = JsonSerializer.Deserialize<bool>(responseContent, jsonSerializerOptions);
return isValid;
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
} catch (Exception ex) {
throw new Exception($"Unable to determine if {number} is a valid ECN#, because {ex.Message}");
}
}
}

View File

@ -0,0 +1,49 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.JSInterop;
namespace MesaFabApproval.Client.Services;
public interface ILocalStorageService {
Task AddItem(string key, string value);
Task AddItem<T>(string key, T value);
Task RemoveItem(string key);
Task<string> GetItem(string key);
Task<T?> GetItem<T>(string key);
}
public class LocalStorageService : ILocalStorageService {
private readonly IJSRuntime _jsRuntime;
public LocalStorageService(IJSRuntime jsRuntime) {
_jsRuntime = jsRuntime;
}
public async Task AddItem(string key, string value) {
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", key, value);
}
public async Task AddItem<T>(string key, T value) {
string item = JsonSerializer.Serialize(value);
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", key, item);
}
public async Task RemoveItem(string key) {
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", key);
}
public async Task<string> GetItem(string key) {
return await _jsRuntime.InvokeAsync<string>("localStorage.getItem", key);
}
public async Task<T?> GetItem<T>(string key) {
string item = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", key);
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<T>(item, jsonSerializerOptions);
}
}

View File

@ -0,0 +1,755 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using MesaFabApproval.Shared.Models;
using MesaFabApproval.Shared.Utilities;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
namespace MesaFabApproval.Client.Services;
public interface IMRBService {
Task<IEnumerable<MRB>> GetAllMRBs(bool bypassCache);
Task<MRB> GetMRBById(int id, bool bypassCache = false);
Task<MRB> GetMRBByTitle(string title, bool bypassCache);
Task CreateNewMRB(MRB mrb);
Task RecallMRB(MRB mrb, User recallUser);
Task DeleteMRB(int mrbNumber);
Task UpdateMRB(MRB mrb);
Task SubmitForApproval(MRB mrb);
Task GenerateActionTasks(MRB mrb, MRBAction action);
Task CreateMRBAction(MRBAction mrbAction);
Task<IEnumerable<MRBAction>> GetMRBActionsForMRB(int mrbNumber, bool bypassCache);
Task UpdateMRBAction(MRBAction mrbAction);
Task DeleteMRBAction(MRBAction mrbAction);
Task UploadAttachments(IEnumerable<IBrowserFile> files, int mrbNumber);
Task UploadActionAttachments(IEnumerable<IBrowserFile> files, int actionId);
Task<IEnumerable<MRBAttachment>> GetAllAttachmentsForMRB(int mrbNumber, bool bypassCache);
Task<IEnumerable<MRBActionAttachment>> GetAllActionAttachmentsForMRB(int mrbNumber, bool bypassCache);
Task DeleteAttachment(MRBAttachment attachment);
Task NotifyNewApprovals(MRB mrb);
Task NotifyApprovers(MRBNotification notification);
Task NotifyOriginator(MRBNotification notification);
Task NotifyQAPreApprover(MRBNotification notification);
Task<bool> NumberIsValid(int number);
}
public class MRBService : IMRBService {
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ISnackbar _snackbar;
private readonly IUserService _userService;
private readonly IApprovalService _approvalService;
public MRBService(IMemoryCache cache,
IHttpClientFactory httpClientFactory,
ISnackbar snackbar,
IUserService userService,
IApprovalService approvalService) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected");
_snackbar = snackbar ?? throw new ArgumentNullException("ISnackbar not injected");
_userService = userService ?? throw new ArgumentNullException("IUserService not injected");
_approvalService = approvalService ?? throw new ArgumentNullException("IApprovalService not injected");
}
public async Task CreateNewMRB(MRB mrb) {
if (mrb is null) throw new ArgumentNullException("MRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, "mrb/new");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(mrb),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to generate new MRB, because {responseMessage.ReasonPhrase}");
}
mrb = await GetMRBByTitle(mrb.Title, true);
_cache.Set($"mrb{mrb.MRBNumber}", mrb, DateTimeOffset.Now.AddHours(1));
_cache.Set($"mrb{mrb.Title}", mrb, DateTimeOffset.Now.AddHours(1));
IEnumerable<MRB>? allMrbs = _cache.Get<IEnumerable<MRB>>("allMrbs");
if (allMrbs is not null) {
List<MRB> mrbList = allMrbs.ToList();
mrbList.Add(mrb);
_cache.Set("allMrbs", mrbList);
}
}
public async Task RecallMRB(MRB mrb, User recallUser) {
if (mrb is null) throw new ArgumentNullException("MRB cannot be null");
if (mrb.StageNo < 1) throw new ArgumentException("MRB already in Draft stage");
if (mrb.StageNo >= 4) throw new Exception("you cannot recall a completed MRB");
mrb.StageNo = 0;
mrb.SubmittedDate = DateTimeUtilities.MIN_DT;
mrb.ApprovalDate = DateTimeUtilities.MAX_DT;
mrb.CloseDate = DateTimeUtilities.MAX_DT;
await UpdateMRB(mrb);
IEnumerable<Approval> approvals = await _approvalService.GetApprovalsForIssueId(mrb.MRBNumber, false);
foreach (Approval approval in approvals) {
if (approval.CompletedDate >= DateTimeUtilities.MAX_DT) {
string comment = $"Recalled by {recallUser.GetFullName()}.";
approval.Comments = comment;
approval.CompletedDate = DateTime.Now;
approval.ItemStatus = -1;
await _approvalService.UpdateApproval(approval);
}
}
string message = $"MRB# {mrb.MRBNumber} [{mrb.Title}] has been recalled by {recallUser.GetFullName()}.";
MRBNotification notification = new() { Message = message, MRB = mrb };
await NotifyApprovers(notification);
}
public async Task DeleteMRB(int mrbNumber) {
if (mrbNumber <= 0) throw new ArgumentException($"{mrbNumber} is not a valid MRB#");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"mrb/delete?mrbNumber={mrbNumber}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to delete MRB# {mrbNumber}, because {responseMessage.ReasonPhrase}");
}
IEnumerable<MRB> allMRBs = await GetAllMRBs(true);
_cache.Set("allMrbs", allMRBs);
}
public async Task<IEnumerable<MRB>> GetAllMRBs(bool bypassCache) {
try {
IEnumerable<MRB>? allMRBs = null;
if (!bypassCache) allMRBs = _cache.Get<IEnumerable<MRB>>("allMrbs");
if (allMRBs is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrb/all?bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
allMRBs = JsonSerializer.Deserialize<IEnumerable<MRB>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse MRBs from API response");
_cache.Set($"allMrbs", allMRBs, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception($"Unable to get all MRBs, because {responseMessage.ReasonPhrase}");
}
}
return allMRBs;
} catch (Exception) {
throw;
}
}
public async Task<bool> NumberIsValid(int number) {
try {
if (number <= 0) return false;
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrb/numberIsValid?number={number}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
bool isValid = JsonSerializer.Deserialize<bool>(responseContent, jsonSerializerOptions);
return isValid;
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
} catch (Exception ex) {
throw new Exception($"Unable to determine if {number} is a valid MRB#, because {ex.Message}");
}
}
public async Task<MRB> GetMRBById(int id, bool bypassCache=false) {
if (id <= 0) throw new ArgumentException($"Invalid MRB number: {id}");
MRB? mrb = null;
if (!bypassCache)
mrb = _cache.Get<MRB>($"mrb{id}");
if (mrb is null && !bypassCache) mrb = _cache.Get<IEnumerable<MRB>>("allMrbs")?.FirstOrDefault(m => m.MRBNumber == id);
if (mrb is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrb/getById?id={id}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
mrb = JsonSerializer.Deserialize<MRB>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse MRB from API response");
_cache.Set($"mrb{mrb.MRBNumber}", mrb, DateTimeOffset.Now.AddHours(1));
} else {
throw new Exception($"Unable to get MRB by Id, because {responseMessage.ReasonPhrase}");
}
}
return mrb;
}
public async Task<MRB> GetMRBByTitle(string title, bool bypassCache) {
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentException("Title cannot be null or empty");
MRB? mrb = null;
if (!bypassCache) mrb = _cache.Get<MRB>($"mrb{title}");
if (mrb is null && !bypassCache)
mrb = _cache.Get<IEnumerable<MRB>>("allMrbs")?.FirstOrDefault(m => m.Title.Equals(title));
if (mrb is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrb/getByTitle?title={title}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
mrb = JsonSerializer.Deserialize<MRB>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse MRB from API response");
_cache.Set($"mrb{mrb.Title}", mrb, DateTimeOffset.Now.AddHours(1));
} else {
throw new Exception($"Unable to get MRB by title, because {responseMessage.ReasonPhrase}");
}
}
return mrb;
}
public async Task UpdateMRB(MRB mrb) {
if (mrb is null) throw new ArgumentNullException("MRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"mrb");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(mrb),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to update MRB, because {responseMessage.ReasonPhrase}");
}
_cache.Set($"mrb{mrb.MRBNumber}", mrb, DateTimeOffset.Now.AddHours(1));
_cache.Set($"mrb{mrb.Title}", mrb, DateTimeOffset.Now.AddHours(1));
IEnumerable<MRB>? allMrbs = _cache.Get<IEnumerable<MRB>>("allMrbs");
if (allMrbs is not null) {
List<MRB> mrbList = allMrbs.ToList();
mrbList.RemoveAll(m => m.MRBNumber == mrb.MRBNumber);
mrbList.Add(mrb);
_cache.Set("allMrbs", mrbList);
}
}
public async Task CreateMRBAction(MRBAction mrbAction) {
if (mrbAction is null) throw new ArgumentNullException("MRB action cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, "mrbAction");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(mrbAction),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to create new MRB action, because {responseMessage.ReasonPhrase}");
await GetMRBActionsForMRB(mrbAction.MRBNumber, true);
}
public async Task<IEnumerable<MRBAction>> GetMRBActionsForMRB(int mrbNumber, bool bypassCache) {
if (mrbNumber <= 0) throw new ArgumentException($"{mrbNumber} is not a valid MRB#");
IEnumerable<MRBAction>? mrbActions = null;
if (!bypassCache)
mrbActions = _cache.Get<IEnumerable<MRBAction>>($"mrbActions{mrbNumber}");
if (mrbActions is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrbAction?mrbNumber={mrbNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
mrbActions = JsonSerializer.Deserialize<IEnumerable<MRBAction>>(responseContent, jsonSerializerOptions) ??
new List<MRBAction>();
if (mrbActions.Count() > 0)
_cache.Set($"mrbActions{mrbNumber}", mrbActions, DateTimeOffset.Now.AddMinutes(5));
} else {
throw new Exception($"Unable to get MRB {mrbNumber} actions, because {responseMessage.ReasonPhrase}");
}
}
return mrbActions;
}
public async Task UpdateMRBAction(MRBAction mrbAction) {
if (mrbAction is null) throw new ArgumentNullException("MRB action cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"mrbAction");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(mrbAction),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) {
throw new Exception($"Unable to update MRB action, because {responseMessage.ReasonPhrase}");
}
IEnumerable<MRBAction>? mrbActions = _cache.Get<IEnumerable<MRBAction>>($"mrbActions{mrbAction.MRBNumber}");
if (mrbActions is not null) {
List<MRBAction> mrbActionList = mrbActions.ToList();
mrbActionList.RemoveAll(a => a.ActionID == mrbAction.ActionID);
mrbActionList.Add(mrbAction);
_cache.Set($"mrbActions{mrbAction.MRBNumber}", mrbActionList, DateTimeOffset.Now.AddMinutes(5));
}
}
public async Task DeleteMRBAction(MRBAction mrbAction) {
if (mrbAction is null) throw new ArgumentNullException("MRB action cannot be null");
if (mrbAction.ActionID <= 0) throw new ArgumentException($"{mrbAction.ActionID} is not a valid MRBActionID");
if (mrbAction.MRBNumber <= 0) throw new ArgumentException($"{mrbAction.MRBNumber} is not a valid MRBNumber");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
string route = $"mrbAction?mrbActionID={mrbAction.ActionID}&mrbNumber={mrbAction.MRBNumber}";
HttpRequestMessage requestMessage = new(HttpMethod.Delete, route);
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to delete MRB action {mrbAction.ActionID}");
IEnumerable<MRBAction>? mrbActions = _cache.Get<IEnumerable<MRBAction>>($"mrbActions{mrbAction.MRBNumber}");
if (mrbActions is not null) {
List<MRBAction> mrbActionList = mrbActions.ToList();
mrbActionList.RemoveAll(a => a.ActionID == mrbAction.ActionID);
_cache.Set($"mrbActions{mrbAction.MRBNumber}", mrbActionList, DateTimeOffset.Now.AddMinutes(5));
}
}
public async Task UploadAttachments(IEnumerable<IBrowserFile> files, int mrbNumber) {
if (files is null) throw new ArgumentNullException("Files cannot be null");
if (files.Count() <= 0) throw new ArgumentException("Files cannot be empty");
if (mrbNumber <= 0) throw new ArgumentException($"{mrbNumber} is not a valid MRB number");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"mrb/attach?mrbNumber={mrbNumber}");
using MultipartFormDataContent content = new MultipartFormDataContent();
foreach (IBrowserFile file in files) {
try {
long maxFileSize = 1024L * 1024L * 1024L * 2L;
StreamContent fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider();
const string defaultContentType = "application/octet-stream";
if (!contentTypeProvider.TryGetContentType(file.Name, out string? contentType)) {
contentType = defaultContentType;
}
fileContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
content.Add(content: fileContent, name: "\"files\"", fileName: file.Name);
} catch (Exception ex) {
_snackbar.Add($"File {file.Name} not saved, because {ex.Message}");
}
}
requestMessage.Content = content;
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to save attachments, because {responseMessage.ReasonPhrase}");
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
IEnumerable<UploadResult> results = JsonSerializer.Deserialize<IEnumerable<UploadResult>>(responseContent, jsonSerializerOptions) ??
new List<UploadResult>();
foreach (UploadResult result in results) {
if (result.UploadSuccessful) {
_snackbar.Add($"{result.FileName} successfully uploaded", Severity.Success);
} else {
_snackbar.Add($"{result.FileName} not uploaded, because {result.Error}", Severity.Error);
}
}
await GetAllAttachmentsForMRB(mrbNumber, true);
}
public async Task UploadActionAttachments(IEnumerable<IBrowserFile> files, int actionId) {
if (files is null) throw new ArgumentNullException("Files cannot be null");
if (files.Count() <= 0) throw new ArgumentException("Files cannot be empty");
if (actionId <= 0) throw new ArgumentException($"{actionId} is not a valid MRB action ID");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"mrb/action/attach?actionId={actionId}");
using MultipartFormDataContent content = new MultipartFormDataContent();
foreach (IBrowserFile file in files) {
try {
long maxFileSize = 1024L * 1024L * 1024L * 2L;
StreamContent fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider();
const string defaultContentType = "application/octet-stream";
if (!contentTypeProvider.TryGetContentType(file.Name, out string? contentType)) {
contentType = defaultContentType;
}
fileContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
content.Add(content: fileContent, name: "\"files\"", fileName: file.Name);
} catch (Exception ex) {
_snackbar.Add($"File {file.Name} not saved, because {ex.Message}");
}
}
requestMessage.Content = content;
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to save action attachments, because {responseMessage.ReasonPhrase}");
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
IEnumerable<UploadResult> results = JsonSerializer.Deserialize<IEnumerable<UploadResult>>(responseContent, jsonSerializerOptions) ??
new List<UploadResult>();
foreach (UploadResult result in results) {
if (result.UploadSuccessful) {
_snackbar.Add($"{result.FileName} successfully uploaded", Severity.Success);
} else {
_snackbar.Add($"{result.FileName} not uploaded, because {result.Error}", Severity.Error);
}
}
await GetAllAttachmentsForMRB(actionId, true);
}
public async Task<IEnumerable<MRBAttachment>> GetAllAttachmentsForMRB(int mrbNumber, bool bypassCache) {
if (mrbNumber <= 0) throw new ArgumentException($"{mrbNumber} is not a valid MRB#");
IEnumerable<MRBAttachment>? mrbAttachments = null;
if (!bypassCache)
mrbAttachments = _cache.Get<IEnumerable<MRBAttachment>>($"mrbAttachments{mrbNumber}");
if (mrbAttachments is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrb/attachments?mrbNumber={mrbNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
mrbAttachments = JsonSerializer.Deserialize<IEnumerable<MRBAttachment>>(responseContent, jsonSerializerOptions) ??
new List<MRBAttachment>();
if (mrbAttachments.Count() > 0)
_cache.Set($"mrbAttachments{mrbNumber}", mrbAttachments, DateTimeOffset.Now.AddMinutes(5));
} else {
throw new Exception($"Unable to get MRB {mrbNumber} attachments, because {responseMessage.ReasonPhrase}");
}
}
return mrbAttachments;
}
public async Task<IEnumerable<MRBActionAttachment>> GetAllActionAttachmentsForMRB(int mrbNumber, bool bypassCache) {
if (mrbNumber <= 0) throw new ArgumentException($"{mrbNumber} is not a valid MRB#");
IEnumerable<MRBActionAttachment>? actionAttachments = null;
if (!bypassCache)
actionAttachments = _cache.Get<IEnumerable<MRBActionAttachment>>($"mrbActionAttachments{mrbNumber}");
if (actionAttachments is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"mrb/action/attachments?mrbNumber={mrbNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
actionAttachments = JsonSerializer.Deserialize<IEnumerable<MRBActionAttachment>>(responseContent, jsonSerializerOptions) ??
new List<MRBActionAttachment>();
if (actionAttachments.Count() > 0)
_cache.Set($"mrbActionAttachments{mrbNumber}", actionAttachments, DateTimeOffset.Now.AddMinutes(5));
} else {
throw new Exception($"Unable to get MRB {mrbNumber} action attachments, because {responseMessage.ReasonPhrase}");
}
}
return actionAttachments;
}
public async Task DeleteAttachment(MRBAttachment attachment) {
if (attachment is null) throw new ArgumentNullException("MRB attachment cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, "mrb/attach");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(attachment),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to delete MRB attachment");
IEnumerable<MRBAttachment>? mrbAttachments = _cache.Get<IEnumerable<MRBAttachment>>($"mrbAttachments{attachment.MRBNumber}");
if (mrbAttachments is not null) {
List<MRBAttachment> mrbAttachmentList = mrbAttachments.ToList();
mrbAttachmentList.RemoveAll(a => a.AttachmentID == attachment.AttachmentID);
_cache.Set($"mrbAttachments{attachment.MRBNumber}", mrbAttachmentList, DateTimeOffset.Now.AddMinutes(5));
}
}
public async Task SubmitForApproval(MRB mrb) {
if (mrb is null) throw new ArgumentNullException("MRB cannot be null");
string roleName = "QA_PRE_APPROVAL";
string subRoleName = "QA_PRE_APPROVAL";
if (mrb.StageNo > 1) {
roleName = "MRB Approver";
subRoleName = "MRBApprover";
}
int roleId = await _approvalService.GetRoleIdForRoleName(roleName);
if (roleId <= 0) throw new Exception($"could not find {roleName} role ID");
IEnumerable<SubRole> subRoles = await _approvalService.GetSubRolesForSubRoleName(subRoleName, roleId);
foreach (SubRole subRole in subRoles) {
IEnumerable<User> members = await _approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User member in members) {
Approval approval = new() {
IssueID = mrb.MRBNumber,
RoleName = roleName,
SubRole = subRole.SubRoleName,
UserID = member.UserID,
SubRoleID = subRole.SubRoleID,
AssignedDate = DateTime.Now,
Step = mrb.StageNo
};
await _approvalService.CreateApproval(approval);
}
}
}
public async Task GenerateActionTasks(MRB mrb, MRBAction action) {
if (mrb is null) throw new ArgumentNullException("MRB cannot be null");
if (action is null) throw new ArgumentNullException("MRBAction cannot be null");
string roleName = "MRB Actions";
string subRoleName = "MRBActions";
int roleId = await _approvalService.GetRoleIdForRoleName(roleName);
if (roleId <= 0) throw new Exception($"could not find {roleName} role ID");
IEnumerable<SubRole> subRoles = await _approvalService.GetSubRolesForSubRoleName(subRoleName, roleId);
foreach (SubRole subRole in subRoles) {
IEnumerable<User> members = await _approvalService.GetApprovalGroupMembers(subRole.SubRoleID);
foreach (User member in members) {
Approval approval = new() {
IssueID = action.MRBNumber,
RoleName = roleName,
SubRole = subRole.SubRoleName,
UserID = member.UserID,
SubRoleID = subRole.SubRoleID,
AssignedDate = DateTime.Now,
Step = mrb.StageNo,
SubRoleCategoryItem = subRole.SubRoleCategoryItem,
TaskID = action.ActionID
};
await _approvalService.CreateApproval(approval);
}
}
}
public async Task NotifyNewApprovals(MRB mrb) {
if (mrb is null) throw new ArgumentNullException("MRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"mrb/notify/new-approvals");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(mrb),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to notify new MRB approvers, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyApprovers(MRBNotification notification) {
if (notification is null) throw new ArgumentNullException("notification cannot be null");
if (notification.MRB is null) throw new ArgumentNullException("MRB cannot be null");
if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"mrb/notify/approvers");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(notification),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to notify MRB approvers, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyOriginator(MRBNotification notification) {
if (notification is null) throw new ArgumentNullException("notification cannot be null");
if (notification.MRB is null) throw new ArgumentNullException("MRB cannot be null");
if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"mrb/notify/originator");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(notification),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to notify MRB originator, because {responseMessage.ReasonPhrase}");
}
public async Task NotifyQAPreApprover(MRBNotification notification) {
if (notification is null) throw new ArgumentNullException("notification cannot be null");
if (notification.MRB is null) throw new ArgumentNullException("MRB cannot be null");
if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"mrb/notify/qa-pre-approver");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(notification),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception($"Unable to notify QA pre approver, because {responseMessage.ReasonPhrase}");
}
}

View File

@ -0,0 +1,101 @@
using System.Security.Claims;
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components.Authorization;
using MudBlazor;
namespace MesaFabApproval.Client.Services;
public class MesaFabApprovalAuthStateProvider : AuthenticationStateProvider, IDisposable {
private readonly IAuthenticationService _authService;
private readonly IUserService _userService;
private readonly ISnackbar _snackbar;
public User? CurrentUser { get; private set; }
public MesaFabApprovalAuthStateProvider(IAuthenticationService authService,
ISnackbar snackbar,
IUserService userService) {
_authService = authService ??
throw new ArgumentNullException("IAuthenticationService not injected");
_snackbar = snackbar ??
throw new ArgumentNullException("ISnackbar not injected");
_userService = userService ??
throw new ArgumentNullException("IUserService not injected");
AuthenticationStateChanged += OnAuthenticationStateChangedAsync;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync() {
ClaimsPrincipal principal = new();
try {
principal = await _authService.FetchAuthState();
CurrentUser = await _authService.GetCurrentUser();
return new(principal);
} catch (Exception ex) {
return new(new ClaimsPrincipal());
}
}
public async Task StateHasChanged(ClaimsPrincipal principal) {
if (principal is null) throw new ArgumentNullException("ClaimsPrincipal cannot be null");
CurrentUser = await _authService.GetCurrentUser();
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal)));
}
public async Task LoginAsync(string loginId, string password) {
try {
if (string.IsNullOrWhiteSpace(loginId)) throw new ArgumentException("LoginId cannot be null or empty");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password cannot be null or empty");
ClaimsPrincipal principal = await _authService.SendAuthenticationRequest(loginId, password);
CurrentUser = await _authService.GetCurrentUser();
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal)));
} catch (Exception ex) {
_snackbar.Add(ex.Message, Severity.Error);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new())));
}
}
public async Task LoginLocal() {
try {
ClaimsPrincipal principal = await _authService.AttemptLocalUserAuth();
CurrentUser = await _authService.GetCurrentUser();
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal)));
} catch (Exception ex) {
_snackbar.Add(ex.Message, Severity.Error);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new())));
}
}
public async Task Logout() {
CurrentUser = null;
await _authService.ClearTokens();
await _authService.ClearCurrentUser();
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new())));
}
public void Dispose() => AuthenticationStateChanged -= OnAuthenticationStateChangedAsync;
private async void OnAuthenticationStateChangedAsync(Task<AuthenticationState> task) {
try {
AuthenticationState authenticationState = await task;
if (authenticationState is not null) {
ClaimsPrincipal principal = await _authService.FetchAuthState();
CurrentUser = await _authService.GetCurrentUser();
}
} catch (Exception ex) {
// _snackbar.Add($"Unable to fetch authentication state, because {ex.Message}", Severity.Error);
}
}
}

View File

@ -0,0 +1,263 @@
using System.Text;
using System.Text.Json;
using MesaFabApproval.Shared.Models;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
namespace MesaFabApproval.Client.Services;
public interface IPCRBService {
Task<string> IdIsValid(string id);
Task<IEnumerable<PCRB>> GetAllPCRBs(bool bypassCache);
Task CreateNewPCRB(PCRB pcrb);
Task<PCRB> GetPCRBByPlanNumber(int planNumber, bool bypassCache);
Task<PCRB> GetPCRBByTitle(string title, bool bypassCache);
Task UpdatePCRB(PCRB pcrb);
Task DeletePCRB(int planNumber);
}
public class PCRBService : IPCRBService {
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ISnackbar _snackbar;
public PCRBService(IMemoryCache cache, IHttpClientFactory httpClientFactory, ISnackbar snackbar) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected");
_snackbar = snackbar ?? throw new ArgumentNullException("ISnackbar not injected");
}
public async Task<string> IdIsValid(string id) {
bool isMatch = true;
if (string.IsNullOrWhiteSpace(id)) isMatch = false;
try {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/getByPlanNumber?planNumber={id}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
PCRB? pcrb = JsonSerializer.Deserialize<PCRB>(responseContent, jsonSerializerOptions);
if (pcrb is null) isMatch = false;
} else {
isMatch = false;
}
} catch (Exception) {
isMatch = false;
}
if (!isMatch) return $"{id} is not a valid PCRB#";
return null;
}
public async Task<IEnumerable<PCRB>> GetAllPCRBs(bool bypassCache) {
try {
IEnumerable<PCRB>? allPCRBs = null;
if (!bypassCache) allPCRBs = _cache.Get<IEnumerable<PCRB>>("allPCRBs");
if (allPCRBs is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/all?bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
allPCRBs = JsonSerializer.Deserialize<IEnumerable<PCRB>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse PCRBs from API response");
_cache.Set($"allPCRBs", allPCRBs, DateTimeOffset.Now.AddMinutes(15));
} else {
throw new Exception(responseMessage.ReasonPhrase);
}
}
return allPCRBs;
} catch (Exception) {
throw;
}
}
public async Task CreateNewPCRB(PCRB pcrb) {
if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(pcrb),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
PCRB newPCRB = await GetPCRBByTitle(pcrb.Title, true);
_cache.Set($"pcrb{pcrb.PlanNumber}", pcrb, DateTimeOffset.Now.AddHours(1));
_cache.Set($"pcrb{pcrb.Title}", pcrb, DateTimeOffset.Now.AddHours(1));
IEnumerable<PCRB>? allPCRBs = _cache.Get<IEnumerable<PCRB>>("allPCRBs");
if (allPCRBs is not null) {
List<PCRB> pcrbList = allPCRBs.ToList();
pcrbList.Add(newPCRB);
_cache.Set("allPCRBs", pcrbList);
}
}
public async Task<PCRB> GetPCRBByPlanNumber(int planNumber, bool bypassCache) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB plan #");
PCRB? pcrb = null;
if (!bypassCache) pcrb = _cache.Get<PCRB>($"pcrb{planNumber}");
if (pcrb is null && !bypassCache)
pcrb = _cache.Get<IEnumerable<PCRB>>("allPCRBs")?.FirstOrDefault(m => m.PlanNumber == planNumber);
if (pcrb is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage =
new(HttpMethod.Get, $"pcrb/getByPlanNumber?planNumber={planNumber}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
pcrb = JsonSerializer.Deserialize<PCRB>(responseContent, jsonSerializerOptions) ??
throw new Exception("unable to parse PCRB from API response");
_cache.Set($"pcrb{pcrb.PlanNumber}", pcrb, DateTimeOffset.Now.AddHours(1));
_cache.Set($"pcrb{pcrb.Title}", pcrb, DateTimeOffset.Now.AddHours(1));
if (bypassCache) {
IEnumerable<PCRB>? allPCRBs = _cache.Get<IEnumerable<PCRB>>("allPCRBs");
if (allPCRBs is not null) {
List<PCRB> pcrbList = allPCRBs.ToList();
pcrbList.RemoveAll(p => p.PlanNumber == planNumber);
pcrbList.Add(pcrb);
_cache.Set("allPCRBs", pcrbList);
}
}
}
return pcrb;
}
public async Task<PCRB> GetPCRBByTitle(string title, bool bypassCache) {
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentNullException("title cannot be null");
PCRB? pcrb = null;
if (!bypassCache) pcrb = _cache.Get<PCRB>($"pcrb{title}");
if (pcrb is null && !bypassCache)
pcrb = _cache.Get<IEnumerable<PCRB>>("allPCRBs")?.FirstOrDefault(m => m.Title.Equals(title));
if (pcrb is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage =
new(HttpMethod.Get, $"pcrb/getByTitle?title={title}&bypassCache={bypassCache}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
pcrb = JsonSerializer.Deserialize<PCRB>(responseContent, jsonSerializerOptions) ??
throw new Exception("unable to parse PCRB from API response");
_cache.Set($"pcrb{pcrb.Title}", pcrb, DateTimeOffset.Now.AddHours(1));
_cache.Set($"pcrb{pcrb.PlanNumber}", pcrb, DateTimeOffset.Now.AddHours(1));
if (bypassCache) {
IEnumerable<PCRB>? allPCRBs = _cache.Get<IEnumerable<PCRB>>("allPCRBs");
if (allPCRBs is not null) {
List<PCRB> pcrbList = allPCRBs.ToList();
pcrbList.RemoveAll(p => p.PlanNumber == pcrb.PlanNumber);
pcrbList.Add(pcrb);
_cache.Set("allPCRBs", pcrbList);
}
}
}
return pcrb;
}
public async Task UpdatePCRB(PCRB pcrb) {
if (pcrb is null) throw new ArgumentNullException("MRB cannot be null");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb");
requestMessage.Content = new StringContent(JsonSerializer.Serialize(pcrb),
Encoding.UTF8,
"application/json");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode)
throw new Exception(responseMessage.ReasonPhrase);
_cache.Set($"pcrb{pcrb.PlanNumber}", pcrb, DateTimeOffset.Now.AddHours(1));
_cache.Set($"pcrb{pcrb.Title}", pcrb, DateTimeOffset.Now.AddHours(1));
IEnumerable<PCRB>? allPCRBs = _cache.Get<IEnumerable<PCRB>>("allPCRBs");
if (allPCRBs is not null) {
List<PCRB> pcrbList = allPCRBs.ToList();
pcrbList.RemoveAll(m => m.PlanNumber == pcrb.PlanNumber);
pcrbList.Add(pcrb);
_cache.Set("allPCRBs", pcrbList);
}
}
public async Task DeletePCRB(int planNumber) {
if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB plan #");
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/delete?planNumber={planNumber}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase);
IEnumerable<PCRB> allPCRBs = await GetAllPCRBs(true);
_cache.Set("allPCRBs", allPCRBs);
}
}

View File

@ -0,0 +1,208 @@
using System.Security.Claims;
using System.Text.Json;
using MesaFabApproval.Shared.Models;
using Microsoft.AspNetCore.Components.WebAssembly.Http;
using Microsoft.Extensions.Caching.Memory;
namespace MesaFabApproval.Client.Services;
public interface IUserService {
ClaimsPrincipal GetClaimsPrincipalFromUser(User user);
string GetLoginIdFromClaimsPrincipal(ClaimsPrincipal claimsPrincipal);
Task<User> GetUserFromClaimsPrincipal(ClaimsPrincipal claimsPrincipal);
Task<User> GetUserByUserId(int userId);
Task<User> GetUserByLoginId(string loginId);
Task<IEnumerable<User>> GetAllActiveUsers();
Task<IEnumerable<int>> GetApproverUserIdsBySubRoleCategoryItem(string item);
}
public class UserService : IUserService {
private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _httpClientFactory;
public UserService(IMemoryCache cache, IHttpClientFactory httpClientFactory) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected");
}
public ClaimsPrincipal GetClaimsPrincipalFromUser(User user) {
if (user is null) throw new ArgumentNullException("user cannot be null");
List<Claim> claims = new() {
new Claim(nameof(user.LoginID), user.LoginID)
};
if (user.IsManager) claims.Add(new Claim(ClaimTypes.Role, "manager"));
if (user.IsAdmin) claims.Add(new Claim(ClaimTypes.Role, "admin"));
ClaimsIdentity identity = new ClaimsIdentity(claims, "MesaFabApprovalWasm");
return new ClaimsPrincipal(identity);
}
public async Task<User> GetUserByUserId(int userId) {
if (userId <= 0) throw new ArgumentException($"{userId} is not a valid user ID");
User? user = _cache.Get<User>($"user{userId}");
if (user is null)
user = _cache.Get<IEnumerable<User>>("allActiveUsers")?.FirstOrDefault(u => u.UserID == userId);
if (user is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"user/userId?userId={userId}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
user = JsonSerializer.Deserialize<User>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse user from API response");
_cache.Set($"user{userId}", user, DateTimeOffset.Now.AddDays(1));
} else {
throw new Exception($"GetUserByUserId failed for user {userId}, because {responseMessage.ReasonPhrase}");
}
}
if (user is null) throw new Exception($"User for userId {userId} not found");
return user;
}
public async Task<User> GetUserByLoginId(string loginId) {
if (string.IsNullOrWhiteSpace(loginId))
throw new ArgumentNullException("loginId cannot be null or empty");
User? user = _cache.Get<User>($"user{loginId}");
if (user is null)
user = _cache.Get<IEnumerable<User>>("allActiveUsers")?.FirstOrDefault(u => u.LoginID == loginId);
if (user is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"user/loginId?loginId={loginId}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
user = JsonSerializer.Deserialize<User>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse user from API response");
_cache.Set($"user{loginId}", user, DateTimeOffset.Now.AddDays(1));
} else {
throw new Exception($"GetUserByLoginId failed for {loginId}, because {responseMessage.ReasonPhrase}");
}
}
if (user is null) throw new Exception($"User for loginId {loginId} not found");
return user;
}
public async Task<IEnumerable<User>> GetAllActiveUsers() {
IEnumerable<User>? activeUsers = _cache.Get<IEnumerable<User>>("allActiveUsers");
if (activeUsers is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"users/active");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
activeUsers = JsonSerializer.Deserialize<IEnumerable<User>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse user from API response");
_cache.Set("allActiveUsers", activeUsers, DateTimeOffset.Now.AddHours(1));
} else {
throw new Exception($"Cannot get all active users, because {responseMessage.ReasonPhrase}");
}
}
if (activeUsers is null)
throw new Exception("unable to fetch all active users");
return activeUsers;
}
public string GetLoginIdFromClaimsPrincipal(ClaimsPrincipal principal) {
if (principal is null) throw new ArgumentNullException("Principal cannot be null");
Claim loginIdClaim = principal.FindFirst("LoginID") ??
throw new Exception("LoginID claim not found in principal");
string loginId = loginIdClaim.Value;
return loginId;
}
public async Task<User> GetUserFromClaimsPrincipal(ClaimsPrincipal claimsPrincipal) {
if (claimsPrincipal is null) throw new ArgumentNullException("ClaimsPrincipal cannot be null");
Claim loginIdClaim = claimsPrincipal.FindFirst("LoginID") ??
throw new Exception("LoginID claim not found in principal");
string loginId = loginIdClaim.Value ??
throw new Exception("LoginID claim value is null");
User user = await GetUserByLoginId(loginId) ??
throw new Exception($"User for loginId {loginId} not found");
return user;
}
public async Task<IEnumerable<int>> GetApproverUserIdsBySubRoleCategoryItem(string item) {
if (string.IsNullOrWhiteSpace(item)) throw new ArgumentException("SubRoleCategoryItem cannot be null or empty");
IEnumerable<int>? approverUserIds = _cache.Get<IEnumerable<int>>($"approvers{item}");
if (approverUserIds is null) {
HttpClient httpClient = _httpClientFactory.CreateClient("API");
HttpRequestMessage requestMessage = new(HttpMethod.Get, $"approver?subRoleCategoryItem={item}");
HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage);
if (responseMessage.IsSuccessStatusCode) {
string responseContent = await responseMessage.Content.ReadAsStringAsync();
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
approverUserIds = JsonSerializer.Deserialize<IEnumerable<int>>(responseContent, jsonSerializerOptions) ??
throw new Exception("Unable to parse user from API response");
_cache.Set($"approvers{item}", approverUserIds, DateTimeOffset.Now.AddDays(1));
} else {
throw new Exception($"Unable to get approvers for SubRoleCategoryItem {item}, because {responseMessage.ReasonPhrase}");
}
}
if (approverUserIds is null) throw new Exception($"Approvers for SubRoleCategoryItem {item} not found");
return approverUserIds;
}
}

View File

@ -0,0 +1,129 @@
using System.Net;
using System.Text.Json;
using System.Text;
using MesaFabApproval.Shared.Models;
using Microsoft.Extensions.Caching.Memory;
using System.Net.Http.Headers;
using MesaFabApproval.Client.Services;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using System.Net.Http;
namespace MesaFabApproval.Client.Utilities;
public class ApiHttpClientHandler : DelegatingHandler {
private readonly IMemoryCache _cache;
private readonly IAuthenticationService _authService;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ISnackbar _snackbar;
private readonly MesaFabApprovalAuthStateProvider _authStateProvider;
private readonly NavigationManager _navigationManager;
private readonly string _apiBaseUrl;
public ApiHttpClientHandler(IMemoryCache cache,
IConfiguration config,
IAuthenticationService authService,
IHttpClientFactory httpClientFactory,
ISnackbar snackbar,
MesaFabApprovalAuthStateProvider authStateProvider,
NavigationManager navigationManager) {
_cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected");
_apiBaseUrl = config["FabApprovalApiBaseUrl"] ??
throw new NullReferenceException("FabApprovalApiBaseUrl not found in config");
_httpClientFactory = httpClientFactory ??
throw new ArgumentNullException("IHttpClientFactory not injected");
_snackbar = snackbar ?? throw new ArgumentNullException("ISnackbar not injected");
_authService = authService ??
throw new ArgumentNullException("IAuthenticationService not injected");
_authStateProvider = authStateProvider ??
throw new ArgumentNullException("MesaFabApprovalAuthStateProvider not injected");
_navigationManager = navigationManager ??
throw new ArgumentNullException("NavigationManager not injected");
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage,
CancellationToken cancellationToken) {
AuthTokens? authTokens = await _authService.GetAuthTokens();
HttpRequestMessage initialRequestMessage = new() {
Content = requestMessage.Content,
Method = requestMessage.Method,
RequestUri = requestMessage.RequestUri
};
if (authTokens is not null) {
initialRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authTokens.JwtToken);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authTokens.JwtToken);
}
HttpClient initialClient = _httpClientFactory.CreateClient("API_Handler");
HttpResponseMessage responseMessage = await initialClient.SendAsync(initialRequestMessage, cancellationToken);
HttpClient refreshClient = _httpClientFactory.CreateClient("API_Handler");
if (responseMessage.StatusCode.Equals(HttpStatusCode.Unauthorized)) {
string? loginId = await _authService.GetLoginId();
if (!string.IsNullOrWhiteSpace(loginId)) {
AuthAttempt authAttempt = new() {
LoginID = loginId,
AuthTokens = authTokens
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/refresh");
request.Content = new StringContent(JsonSerializer.Serialize(authAttempt),
Encoding.UTF8,
"application/json");
if (authTokens is not null) {
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authTokens.JwtToken);
}
HttpResponseMessage httpResponseMessage = await refreshClient.SendAsync(request, cancellationToken);
string responseContent = await httpResponseMessage.Content.ReadAsStringAsync();
if (httpResponseMessage.IsSuccessStatusCode) {
JsonSerializerOptions jsonSerializerOptions = new() {
PropertyNameCaseInsensitive = true
};
LoginResult loginResult = JsonSerializer.Deserialize<LoginResult>(responseContent, jsonSerializerOptions) ??
throw new Exception("unable to parse login result from API response");
if (!loginResult.IsAuthenticated) throw new Exception($"User with Login ID {loginId} not authorized");
if (loginResult.AuthTokens is not null) {
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", loginResult.AuthTokens.JwtToken);
await _authService.SetTokens(loginResult.AuthTokens.JwtToken, loginResult.AuthTokens.RefreshToken);
}
if (loginResult.User is not null)
await _authService.SetCurrentUser(loginResult.User);
} else {
await _authStateProvider.Logout();
string? redirectUrl = _cache.Get<string>("redirectUrl");
if (!string.IsNullOrWhiteSpace(redirectUrl)) {
_navigationManager.NavigateTo($"login/{redirectUrl}");
} else {
_navigationManager.NavigateTo("login");
}
}
}
return await base.SendAsync(requestMessage, cancellationToken);
}
initialClient.Dispose();
refreshClient.Dispose();
return responseMessage;
}
}

View File

@ -0,0 +1,22 @@
@using System.IdentityModel.Tokens.Jwt
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using MesaFabApproval.Client
@using MesaFabApproval.Client.Layout
@using MesaFabApproval.Client.Pages.Components
@using MesaFabApproval.Client.Services
@using MesaFabApproval.Shared.Utilities
@using MesaFabApproval.Shared.Models
@using MudBlazor
@using Microsoft.AspNetCore.Authorization
@using System.Security.Claims
@using Microsoft.Extensions.Caching.Memory
@using Microsoft.AspNetCore.WebUtilities
@attribute [Authorize]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,4 @@
{
"OldFabApprovalUrl": "https://mesaapproval-test.mes.infineon.com",
"FabApprovalApiBaseUrl": "https://mesaapproval-test.mes.infineon.com:7114"
}

View File

@ -0,0 +1,103 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}
a, .btn-link {
color: #0071c1;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.loading-progress {
position: relative;
display: block;
width: 8rem;
height: 8rem;
margin: 20vh auto 1rem auto;
}
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
text-align: center;
font-weight: bold;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after {
content: var(--blazor-load-percentage-text, "Loading");
}
code {
color: #c02d76;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mesa Fab Approval</title>
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="css/app.css" />
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
<link rel="manifest" href="site.webmanifest">
<link href="MesaFabApproval.Client.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
<script>
if (window.location.hostname.includes("localhost")) {
Blazor.start({
webAssembly: {
environment: "Development"
}
});
} else {
Blazor.start();
}
</script>
</body>
</html>

View File

@ -0,0 +1,5 @@
export function OpenInNewWindow(url, message) {
var newwindow = window.open('', '_blank');
newwindow.document.write(message);
newwindow.location.href = url;
}

View File

@ -0,0 +1 @@
{"name":"Mesa Fab Approval","short_name":"MesaFabApproval","icons":[{"src":"android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}