initial upload

This commit is contained in:
kkb 2023-09-21 18:58:32 +02:00
parent a0aa9cc28e
commit f857f43df4
553 changed files with 46169 additions and 13 deletions

View file

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "7.0.5",
"commands": [
"dotnet-ef"
]
}
}
}

View file

@ -0,0 +1,14 @@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">404</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

View file

@ -0,0 +1,47 @@
<MudLink Href="@Href" OnClick="OnClick" Underline="Underline.None">
<MudPaper Elevation="25">
<MudCard Elevation="0">
<MudCardHeader>
<CardHeaderAvatar>
@if (Icon is not null)
{
<MudIcon Icon="@Icon" Color="@(IconColor ?? Color.Inherit)" Style="width:4vh; height:4vh;" />
}
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary" Color="@(KeyColor ?? Color.Inherit)" Style="font-size: 0.96rem;">
@Key
</MudText>
<MudTextField T="string" Variant="Variant.Text" DisableUnderLine ReadOnly
Text="@Text" Lines="@Lines" Class="mt-n1" Style="font-size: 0.99rem; margin: 0;" />
</CardHeaderContent>
</MudCardHeader>
</MudCard>
</MudPaper>
</MudLink>
@code {
[Parameter]
public string? Href { get; set; }
[Parameter]
public EventCallback OnClick { get; set; }
[Parameter]
public string? Icon { get; set; }
[Parameter]
public Color? IconColor { get; set; }
[Parameter]
public string? Key { get; set; }
[Parameter]
public Color? KeyColor { get; set; }
[Parameter]
public string? Text { get; set; }
[Parameter]
public int Lines { get; set; } = 1;
}

View file

@ -0,0 +1,57 @@
@typeparam T
<MudLink Href="@Href" OnClick="OnClick" Underline="Underline.None">
<MudPaper Elevation="25">
<MudCard Elevation="0">
<MudCardHeader>
<CardHeaderAvatar>
@if (Icon is not null)
{
<MudIcon Icon="@Icon" Color="@(IconColor ?? Color.Inherit)" Style="font-size: 2.50rem;" />
}
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary" Color="@(KeyColor ?? Color.Inherit)" Style="font-size: 0.96rem;">
@Key
</MudText>
<MudText Color="@(ValueColor ?? Color.Primary)" Style="font-size: 0.99rem;">
@if (Value is null)
{
@("-")
}
else
{
@Value
}
</MudText>
</CardHeaderContent>
</MudCardHeader>
</MudCard>
</MudPaper>
</MudLink>
@code{
[Parameter]
public string? Href { get; set; }
[Parameter]
public EventCallback OnClick { get; set; }
[Parameter]
public string? Icon { get; set; }
[Parameter]
public Color? IconColor { get; set; }
[Parameter]
public string? Key { get; set; }
[Parameter]
public Color? KeyColor { get; set; }
[Parameter]
public T? Value { get; set; }
[Parameter]
public Color? ValueColor { get; set; }
}

View file

@ -0,0 +1,51 @@
<PageTitle>@Title</PageTitle>
@*@if (Loading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" />
<div class="center-screen" style="height: calc(100vh - 70px);">
<MudProgressCircular Color="Color.Primary" Size="Size.Large" Indeterminate="true" />
</div>
return;
}*@
<div class="mt-7 mb-7 ml-4">
<div class="d-flex mb-3">
<div>
@* <MudStack Row="true">
<MudBreadcrumbs Items="Breadcrumbs" Separator="" Style="padding: 0px;">
<ItemTemplate Context="item">
<MudChip Href="@(item.Href)" Disabled="@(item.Disabled)" Size="Size.Medium" Variant="Variant.Outlined" SelectedColor="Color.Inherit" Color="Color.Transparent">
@item.Text
</MudChip>
</ItemTemplate>
</MudBreadcrumbs>
</MudStack>*@
<MudStack Row="true">
<MudBreadcrumbs Items="Breadcrumbs" Style="padding: 0px;" />
</MudStack>
</div>
@if (LoadData is not null && DisableReload is false)
{
<div class="ml-auto">
@if (Loading)
{
<MudProgressCircular Color="Color.Primary" Size="Size.Small" Indeterminate="true" Style="padding: 0px; display: inherit;" />
}
else
{
<MudIconButton OnClick="OnRefreshAsync" Icon="@Icons.Material.Filled.Refresh" Size="Size.Medium" Color="Color.Inherit" Style="padding: 0px;" />
}
</div>
}
</div>
</div>
<div class="mt-7">
@if (Content is not null)
{
@Content
}
</div>

View file

@ -0,0 +1,74 @@
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Insight.Web.Components.Containers;
public partial class BaseContainer
{
[Parameter]
public string Title { get; set; } = Global.Name;
[Parameter]
public List<BreadcrumbItem>? Breadcrumbs { get; set; }
[Parameter]
public RenderFragment? BreadcrumbAction { get; set; }
[Parameter]
public RenderFragment? Content { get; set; }
[Parameter]
public Func<Task>? LoadData { get; set; }
[Parameter]
public bool DisableReload { get; set; }
private bool _loading;
private bool Loading
{
get
{
return _loading;
}
set
{
if (value != _loading)
{
_loading = value;
StateHasChanged();
}
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await OnRefreshAsync();
}
}
private async Task OnRefreshAsync()
{
if (LoadData is null || Loading) return;
Loading = true;
StateHasChanged();
//var start = Stopwatch.GetTimestamp();
await LoadData();
//var time = Stopwatch.GetElapsedTime(start);
//if (time <= TimeSpan.FromSeconds(1))
//{
// await Task.Delay(TimeSpan.FromSeconds(1).Subtract(time));
//}
Loading = false;
StateHasChanged();
}
}

View file

@ -0,0 +1,66 @@
@typeparam T
<PageTitle>@Title</PageTitle>
<div class="mt-7 mb-6 ml-4">
<div class="d-flex mb-3">
<div>
<MudStack Row="true">
<MudBreadcrumbs Items="Breadcrumbs" Style="padding: 0px;" />
@if (OnAdd.HasDelegate)
{
@if (AddBreadcrumbs is not null)
{
<MudBreadcrumbs Items="AddBreadcrumbs" Class="ml-n3" Style="padding: 0px;" />
}
<MudIconButton OnClick="OnAddClick" Icon="@Icons.Material.Filled.Add" Size="Size.Small" Color="Color.Primary" Class="ml-n4" />
}
</MudStack>
</div>
<div class="ml-auto">
<MudTextField T="string" Value="@Search" ValueChanged="@(s=>SearchAsync(s))" DebounceInterval="250" Immediate Variant="Variant.Text" Clearable OnBlur="OnSearchReleased"
Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Small" Style="margin-top: -15px; width: 40vh;"/>
</div>
</div>
</div>
<MudTable T="T" @ref="Table" ServerData="OnLoadDataAsync" RowsPerPage="50" Loading="Loading" Elevation="0" Dense Square FixedHeader="true" FixedFooter="true" Height="calc(100vh - 205px)">
<HeaderContent>
@if (Header is not null)
{
@Header
}
<MudTh Style="text-align: right; width: 100px;">
<MudStack Row="true" Class="mr-6">
<MudButton OnClick="OnFilterClick" Variant="Variant.Text" Size="Size.Small" Color="@(Filtered ? Color.Warning : Color.Inherit)" DisableElevation Disabled="@(!OnFilter.HasDelegate)">
Filter
</MudButton>
<MudButton OnClick="RefreshAsync" Variant="Variant.Text" Size="Size.Small" DisableElevation Disabled="@(DisableRefresh || Loading)">
Refresh
</MudButton>
</MudStack>
</MudTh>
</HeaderContent>
<RowTemplate>
@if (RowTemplate is not null)
{
@RowTemplate(context)
}
<MudTd DataLabel="Actions" Style="text-align: right;">
@if (ActionTemplate is not null)
{
<MudMenu Label="Actions" Variant="Variant.Text" Size="Size.Small" Dense="true" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" IconColor="Color.Primary"
ActivationEvent="@MouseEvent.MouseOver" AnchorOrigin="Origin.BottomRight" FullWidth="true" Disabled="@(DisableAction)">
@if (ActionTemplate is not null)
{
@ActionTemplate(context)
}
</MudMenu>
}
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager RowsPerPageString="" />
</PagerContent>
</MudTable>

View file

@ -0,0 +1,194 @@
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Insight.Web.Components.Containers;
public partial class TableContainer<T>
{
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
[Parameter]
public string Title { get; set; } = Global.Name;
[Parameter]
public List<BreadcrumbItem>? Breadcrumbs { get; set; }
[Parameter]
public Func<TableState, Task<TableData<T>>>? Data { get; set; }
[Parameter]
public RenderFragment? Header { get; set; }
[Parameter]
public RenderFragment<T>? RowTemplate { get; set; }
[Parameter]
public RenderFragment<T>? ActionTemplate { get; set; }
[Parameter]
public bool DisableAction { get; set; }
private List<BreadcrumbItem>? AddBreadcrumbs { get; set; }
private MudTable<T>? Table { get; set; }
private bool Loading { get; set; }
private int CurrentPage { get; set; }
private int CurrentSize { get; set; }
protected override void OnInitialized()
{
if (OnAdd.HasDelegate)
{
AddBreadcrumbs = new List<BreadcrumbItem>()
{
new BreadcrumbItem("", "#", true),
new BreadcrumbItem("", "#", true)
};
}
if (NavigationManager.GetQueryString().TryGetValue("search", out var search))
{
Search = search.ToString();
}
//if (NavigationManager.GetQueryString().TryGetValue("page", out var pageRaw))
//{
// if (int.TryParse(pageRaw, out var page))
// {
// CurrentPage = page;
// }
//}
//if (NavigationManager.GetQueryString().TryGetValue("size", out var sizeRaw))
//{
// if (int.TryParse(sizeRaw, out var size))
// {
// CurrentSize = size == 0 ? 100 : size;
// }
//}
}
private async Task<TableData<T>> OnLoadDataAsync(TableState state)
{
if (Data is null)
{
throw new MissingMethodException(nameof(Data));
}
try
{
Loading = true;
//state.Page = CurrentPage;
//Table.SetRowsPerPage(CurrentSize);
var data = await Data(state);
//CurrentPage = state.Page;
//CurrentSize = state.PageSize;
//NavigationManager.ChangeQueryStringValue("page", state.Page.ToString());
//NavigationManager.ChangeQueryStringValue("size", state.PageSize.ToString());
//if (state.SortLabel is not null) NavigationManager.ChangeQueryStringValue("sort", state.SortLabel.ToString());
//if (state.SortDirection) NavigationManager.ChangeQueryStringValue("direction", state.SortDirection.ToString());
return data;
}
finally
{
Loading = false;
}
}
//
[Parameter]
public bool DisableRefresh { get; set; }
private string? _search;
[Parameter]
public string? Search
{
get => _search;
set
{
if (_search == value) return;
_search = value;
SearchChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<string?> SearchChanged { get; set; }
private async Task SearchAsync(string? text)
{
Search = text;
if (Loading) return;
await RefreshAsync();
}
public async Task RefreshAsync()
{
if (Loading || Table is null) return;
Loading = true;
StateHasChanged();
//var start = Stopwatch.GetTimestamp();
await Table.ReloadServerData();
//var time = Stopwatch.GetElapsedTime(start);
//if (time <= TimeSpan.FromSeconds(1))
//{
// await Task.Delay(TimeSpan.FromSeconds(1).Subtract(time));
//}
Loading = false;
StateHasChanged();
}
private void OnSearchReleased()
{
NavigationManager.ChangeQueryStringValue("search", Search);
}
//
[Parameter] public EventCallback OnFilter { get; set; }
[Parameter]
public bool Filtered { get; set; }
private async Task OnFilterClick()
{
if (OnFilter.HasDelegate)
{
await OnFilter.InvokeAsync(this);
}
}
//
[Parameter]
public EventCallback OnAdd { get; set; }
private async Task OnAddClick()
{
if (OnAdd.HasDelegate)
{
await OnAdd.InvokeAsync(this);
}
}
}

View file

@ -0,0 +1,25 @@
@if (@Actions is not null)
{
<MudDialog @bind-IsVisible="Visible" Style="max-width: inherit !important;">
<DialogContent>
<MudStack Class="mt-n3 mb-3">
@Content
</MudStack>
</DialogContent>
<DialogActions>
<MudStack Row="true" Class="mb-1">
@Actions
</MudStack>
</DialogActions>
</MudDialog>
}
else
{
<MudDialog @bind-IsVisible="Visible" Style="max-width: inherit !important;">
<DialogContent>
<MudStack Class="mt-n3 mb-3">
@Content
</MudStack>
</DialogContent>
</MudDialog>
}

View file

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Components;
namespace Insight.Web.Components.Dialogs;
public partial class ActionDialog
{
private bool _visible;
[Parameter]
public bool Visible
{
get => _visible;
set
{
if (_visible == value) return;
_visible = value;
VisibleChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<bool> VisibleChanged { get; set; }
[Parameter]
public RenderFragment? Content { get; set; }
[Parameter]
public RenderFragment? Actions { get; set; }
}

View file

@ -0,0 +1,154 @@
@using Insight.Web.Models.Account;
<MudDrawer @bind-Open="_visible" Anchor="Anchor.End" Elevation="0" Variant="@DrawerVariant.Temporary" ClipMode="DrawerClipMode.Always" MiniWidth="400px" Width="@_variableWidth" Style="max-width:auto;">
@if (_content == Content.Contacts)
{
<MudDrawerHeader>
<MudText Typo="Typo.h6">
<MudLink OnClick="() => _content = Content.Contacts" Underline="Underline.None" Typo="Typo.h6" Color="Color.Dark">
Chat
</MudLink>
</MudText>
</MudDrawerHeader>
@* <MudToolBar Dense="true">
<MudText Typo="Typo.h6" Inline="true" Class="mr-2">#</MudText>
<MudText Typo="Typo.h6">contacts</MudText>
</MudToolBar> *@
<MudStack Justify="Justify.Center" Class="px-6">
<MudList Clickable="true" Style="max-height: calc(100vh - 220px); overflow-y: scroll;">
@foreach (var user in ChatService.Users.Keys.Where(p => p.Uid != SessionHandler.State.Uid).OrderByDescending(p => p.Online))
{
<MudListItem OnClick="@(async () => await GetSessionAsync(user))">
<div class="d-flex flex-row mt-n1 mb-n1">
<div class="mr-4">
<MudBadge Class="my-2" Overlap Visible="true" Color="@(user.Online ? Color.Success : Color.Error)">
@* Content="3" *@
<MudAvatar Color="Color.Primary" Style="height:50px; width:50px;">
@user.Username?.ToUpper().FirstOrDefault()
</MudAvatar>
</MudBadge>
</div>
<div>
<MudText Typo="Typo.body1" Style="font-weight: normal;">
@user.Username
</MudText>
<MudText Typo="Typo.body1" Style="font-size: x-small!important;">
@user.Uid
</MudText>
</div>
</div>
</MudListItem>
}
</MudList>
</MudStack>
}
else if (_content == Content.Chat && _currentSession is not null)
{
<MudDrawerHeader>
<MudText Typo="Typo.h6">
<MudLink OnClick="() => _content = Content.Contacts" Underline="Underline.None" Typo="Typo.h6">
Chat
</MudLink>
(@_currentSession.Members.FirstOrDefault(p => p.Uid != SessionHandler.State.Uid)?.Username)
<br>
<MudText Typo="Typo.body1" Style="font-size: x-small!important;">
@_currentSession?.Id.ToString()
</MudText>
</MudText>
</MudDrawerHeader>
<MudStack Justify="Justify.Center" Class="px-6">
<MudList Clickable="false" Style="height: calc(100vh - 260px); overflow-y: scroll;" id="chatContainer">
@foreach (var message in _currentSession.Messages.OrderBy(p => p.CreatedDate))
{
if (message.SenderId == SessionHandler.State.Uid)
{
<MudListItem Dense="true">
<div class="d-flex my-4" style="flex-direction:row-reverse;">
<div class="ml-4">
<MudAvatar Color="Color.Secondary" Style="height:50px; width:50px;">
@SessionHandler.State.Username?.ToUpper().FirstOrDefault()
</MudAvatar>
</div>
<div>
<MudText Align="Align.End" Typo="Typo.body1" Style="font-weight: bold;">
You
</MudText>
<MudText Align="Align.End" Typo="Typo.body1" Style="font-size: x-small!important;">
@message.CreatedDate.ToString("dd MMM yyyy - hh:mm tt")
</MudText>
<MudText Align="Align.End" Typo="Typo.body2" Style="padding: 15px; background-color: var(--mud-palette-background-grey); border-radius: 5px; margin-top:5px">
@message.Message
</MudText>
</div>
</div>
</MudListItem>
}
else
{
var sender = ChatService.Users.Keys.FirstOrDefault(p => p.Uid == message.SenderId);
<MudListItem Dense="true">
<div class="d-flex my-4" style="flex-direction:row;">
<div class="mr-4">
<MudBadge Class="my-2" Overlap Visible="true" Color="@(sender.Online ? Color.Success : Color.Error)">
<MudAvatar Color="Color.Primary" Style="height:50px; width:50px;">
@sender?.Username?.ToUpper().FirstOrDefault()
</MudAvatar>
</MudBadge>
</div>
<div>
<MudText Align="Align.Start" Typo="Typo.body1" Style="font-weight: bold;">
@sender?.Username
</MudText>
<MudText Align="Align.Start" Typo="Typo.body1" Style="font-size: x-small!important;">
@message.CreatedDate.ToString("dd MMM yyyy - hh:mm tt")
</MudText>
<MudText Align="Align.Start" Typo="Typo.body2" Style="padding: 15px; background-color: var(--mud-palette-background-grey); border-radius: 5px; margin-top:5px;">
@message.Message
</MudText>
</div>
</div>
</MudListItem>
}
}
</MudList>
<MudPaper Elevation="25" Class="d-flex flex-row mx-4" Style="">
<MudTextField T="string" @bind-Value="_currentMessage" For="@(()=> _currentMessage)" AutoFocus Immediate Lines="4" TextUpdateSuppression="false" Placeholder="Enter your message..." DisableUnderLine="true" Class="mt-n2 mx-4" OnKeyDown="OnKeyDown" />
<MudButton OnClick="SendAsync" Color="Color.Secondary" ButtonType="ButtonType.Button">
Send
</MudButton>
</MudPaper>
</MudStack>
}
</MudDrawer>
<audio id="chat_user_online" src="/media/chat_user_online.mp3" />
<audio id="chat_message" src="/media/chat_message.mp3" />
@code{
private bool _visible;
private string _variableWidth => _content switch
{
Content.Chat => "600px",
_ => "400px"
};
public void Toggle()
{
_visible = !_visible;
StateHasChanged();
}
async void OnKeyDown(KeyboardEventArgs args)
{
if (args.ShiftKey && args.Key == "Enter")
{
await Task.Delay(100);
await SendAsync();
}
}
}

View file

@ -0,0 +1,197 @@
using Insight.Infrastructure.Services;
using Insight.Web.Constants;
using Insight.Web.Models;
using Insight.Web.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.JSInterop;
using MongoDB.Driver;
using MudBlazor;
using Vaitr.Bus;
using static Insight.Web.Constants.Events.Chat;
namespace Insight.Web.Components.Dialogs;
public partial class ChatDialog
{
[Inject] private Bus Bus { get; init; } = default!;
[Inject] private ChatService ChatService { get; init; } = default!;
[Inject] private SessionPool SessionCache { get; init; } = default!;
[Inject] private SessionHandler SessionHandler { get; init; } = default!;
[Inject] private IdentityService IdentityService { get; init; } = default!;
[Inject] private AuthenticationStateProvider AuthenticationStateProvider { get; init; } = default!;
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private IJSRuntime JSRuntime { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private ILogger<ChatDialog> Logger { get; init; } = default!;
private int _newMessages;
public int NewMessages
{
get
{
return _newMessages;
}
set
{
if (value != _newMessages)
{
_newMessages = value;
//Bus.Publish(Events.Sessions.Changed); // test, bugt hölle
}
}
}
private enum Content { Contacts, Chat }
private Content _content = Content.Contacts;
private ChatUser? _currentUser;
private ChatSession? _currentSession;
private string? _currentMessage;
private readonly List<IDisposable> _subscriptions = new();
protected override async Task OnInitializedAsync()
{
_subscriptions.Add(Bus.SubscribeAsync<ChatUserConnected>(UserConnected, p => p.User.Uid != SessionHandler.State.Uid));
_subscriptions.Add(Bus.SubscribeAsync<ChatUserDisconnected>(UserDisconnected, p => p.User.Uid != SessionHandler.State.Uid));
_subscriptions.Add(Bus.SubscribeAsync<ChatRefresh>(RefreshAsync, null));
_subscriptions.Add(Bus.SubscribeAsync<ChatMessageReceived>(MessageReceived, p => p.Message.SenderId != SessionHandler.State.Uid));
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
}
else
{
_currentUser = ChatService.Users.FirstOrDefault(p => p.Key.Uid == SessionHandler.State.Uid).Key;
if (_content == Content.Chat)
{
await JSRuntime.InvokeAsync<string>("ScrollToBottom", "chatContainer");
// mock
NewMessages = 0;
}
}
}
private async Task GetSessionAsync(ChatUser user)
{
var members = new List<ChatUser>() { _currentUser, user };
var session = await ChatService.AddOrGetSession(members);
_currentSession = session;
_content = Content.Chat;
await InvokeAsync(StateHasChanged);
}
private async Task SendAsync()
{
if (_currentSession is null || string.IsNullOrWhiteSpace(_currentMessage)) return;
await _currentSession.SendMessage(_currentUser, _currentMessage, default);
_currentMessage = string.Empty;
await InvokeAsync(StateHasChanged);
//await JSRuntime.InvokeAsync<string>("ScrollToBottom", "chatContainer");
}
private async ValueTask UserConnected(ChatUserConnected model, CancellationToken cancellationToken)
{
try
{
Notification.Information(Snackbar, $"{model.User.Username} Online", options =>
{
//options.Onclick = (x) => null;
options.HideIcon = true;
options.ShowCloseIcon = false;
options.RequireInteraction = false;
options.ShowTransitionDuration = 0;
options.HideTransitionDuration = 1000;
options.VisibleStateDuration = 10000;
});
_ = JSRuntime.InvokeAsync<string>("PlayAudio", "chat_user_online");
}
catch (Exception ex)
{
Logger.LogError(ex.Message);
}
}
private async ValueTask UserDisconnected(ChatUserDisconnected model, CancellationToken cancellationToken)
{
try
{
Notification.Information(Snackbar, $"{model.User.Username} Offline", options =>
{
//options.Onclick = (x) => null;
options.HideIcon = true;
options.ShowCloseIcon = false;
options.RequireInteraction = false;
options.ShowTransitionDuration = 0;
options.HideTransitionDuration = 1000;
options.VisibleStateDuration = 10000;
});
_ = JSRuntime.InvokeAsync<string>("PlayAudio", "chat_user_online");
}
catch (Exception ex)
{
Logger.LogError(ex.Message);
}
}
private async ValueTask RefreshAsync(ChatRefresh model, CancellationToken cancellationToken)
{
_ = InvokeAsync(StateHasChanged);
}
private async ValueTask MessageReceived(ChatMessageReceived model, CancellationToken cancellationToken)
{
try
{
Notification.Information(Snackbar, $"New message from: {model.Session.Members.FirstOrDefault(p => p.Uid == model.Message.SenderId)?.Username}", options =>
{
options.Onclick = (x) => OpenSpecificChatWindow(model.Session);
options.HideIcon = true;
options.ShowCloseIcon = true;
options.RequireInteraction = true;
options.ShowTransitionDuration = 0;
options.HideTransitionDuration = 0;
options.VisibleStateDuration = 10000;
options.DuplicatesBehavior = SnackbarDuplicatesBehavior.Prevent;
});
// mock
NewMessages++;
if (_content != Content.Chat)
{
_ = JSRuntime.InvokeAsync<string>("PlayAudio", "chat_message");
}
_ = InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
}
private async Task OpenSpecificChatWindow(ChatSession session)
{
_currentSession = session;
_content = Content.Chat;
_visible = true;
await InvokeAsync(StateHasChanged);
}
}

View file

@ -0,0 +1,8 @@
@inherits LayoutComponentBase
<MudSnackbarProvider />
<ThemeProvider DisableIcon />
<MudLayout>
@Body
</MudLayout>

View file

@ -0,0 +1,82 @@
@using Vaitr.Bus;
@inherits LayoutComponentBase
@implements IDisposable
@inject Bus Bus
<MudSnackbarProvider />
<MudDialogProvider />
<MudLayout>
<MudAppBar Elevation="0" Fixed>
<MudLink Href="@Navigation.Home" Typo="Typo.h6" Underline="Underline.None" Class="mr-4" Style="color: white !important; text-transform:uppercase;">
@Global.Name
</MudLink>
<AuthorizeView>
<Authorized>
<MudIconButton OnClick="()=>_drawer?.Toggle()" Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" />
</Authorized>
</AuthorizeView>
<MudSpacer />
<ChatProvider />
<ThemeProvider />
<ProfileProvider />
</MudAppBar>
<AuthorizeView>
<Authorized>
<CascadingValue Value="RouteValues">
<DrawerProvider @ref="_drawer" />
</CascadingValue>
</Authorized>
</AuthorizeView>
<MudMainContent>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
@Body
</MudContainer>
</MudMainContent>
</MudLayout>
@code{
public IReadOnlyDictionary<string, object>? RouteValues { get; set; }
private DrawerProvider? _drawer;
private bool _disposed;
protected override void OnParametersSet()
{
RouteValues = (Body?.Target as RouteView)?.RouteData.RouteValues;
base.OnParametersSet();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
}
await Bus.PublishAsync(Events.Layout.Rendered).ConfigureAwait(false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// auto disposed when starved circuit timeouts (disconnects)
if (_disposed is false)
{
if (disposing)
{
}
_disposed = true;
}
}
}

View file

@ -0,0 +1,7 @@
@inherits ComponentBase
<MudNavLink Icon="@Icons.Material.Filled.Person" IconColor="Color.Inherit" Match="NavLinkMatch.All" Class="pl-3">
Account
</MudNavLink>
<MudNavLink Href=@Navigation.Account.Profile Match="NavLinkMatch.Prefix">Profile</MudNavLink>

View file

@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
namespace Insight.Web.Components.Navbars;
public partial class Account
{
[CascadingParameter] public IReadOnlyDictionary<string, object>? RouteValues { get; set; }
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
[Inject] private AuthenticationStateProvider AuthenticationStateProvider { get; init; } = default!;
private string Uri { get; set; } = string.Empty;
private string Title { get; set; } = "Account";
protected override async Task OnInitializedAsync()
{
var cp = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User;
//Title = $"Account: {cp?.Identity?.Name}";
Uri = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
}
protected override void OnAfterRender(bool firstRender)
{
Uri = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
}
}

View file

@ -0,0 +1,13 @@
@inherits ComponentBase
@*<MudNavLink Icon="@Icons.Material.Outlined.Business" IconColor="Color.Secondary" Match="NavLinkMatch.All" Class="pl-2">
@Title
</MudNavLink>*@
<MudNavLink Href=@Navigation.Management.Customers.DetailsHref(Id) Icon="@Icons.Material.Outlined.Business" IconColor="Color.Inherit" Match="NavLinkMatch.All" Class="pl-3">
@Title
</MudNavLink>
<div class="mt-3">
<MudNavLink Href=@Navigation.Management.Customers.HostsHref(Id) Match="NavLinkMatch.All">Hosts</MudNavLink>
</div>

View file

@ -0,0 +1,62 @@
using Insight.Infrastructure;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Driver;
using Vaitr.Bus;
namespace Insight.Web.Components.Navbars;
public partial class Customer
{
[CascadingParameter] public IReadOnlyDictionary<string, object>? RouteValues { get; set; }
[Inject] private IMongoDatabase Database { get; set; } = default!;
[Inject] private Bus Bus { get; init; } = default!;
private string Title { get; set; } = "Customer";
private string? Id { get; set; }
private IDisposable? Token { get; set; }
public bool Disposed { get; set; } = false;
protected override void OnInitialized()
{
Token = Bus.Subscribe<string>(OnRefresh, p => p == Events.Layout.Rendered);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (RouteValues is not null && Id is null)
{
if (RouteValues.TryGetValue("customerId", out object? rawId) == false) return;
Id = rawId?.ToString();
var entity = await Database.Customer()
.Find(p => p.Id == Id)
.FirstOrDefaultAsync(default);
Title = $"{entity?.Name}";
await InvokeAsync(() => StateHasChanged());
}
}
public void OnRefresh(string? message)
{
InvokeAsync(() => StateHasChanged());
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed) return;
if (disposing is false) return;
Token?.Dispose();
}
}

View file

@ -0,0 +1,60 @@
@inherits ComponentBase
@*@if (CustomerEntity is not null)
{
<MudNavLink Icon="@Icons.Material.Outlined.Business" Href="@Navigation.Management.Customers.DetailsHref(CustomerEntity.DocumentId.ToString())" Class="pl-2">
@CustomerEntity.Name
</MudNavLink>
}*@
@if (HostEntity is not null)
{
<MudNavLink Icon="@Icons.Material.Outlined.Devices" IconColor="Color.Inherit" Href=@Navigation.Management.Hosts.DetailsHref(Id) Match="NavLinkMatch.All" Class="pl-3">
@HostEntity.Name
</MudNavLink>
}
<div class="mt-3">
<MudNavGroup Title="Actions">
<MudNavLink Href=@Navigation.Management.Hosts.Actions.Console.IndexHref(Id) Match="NavLinkMatch.Prefix">Console</MudNavLink>
</MudNavGroup>
</div>
<div class="mt-1">
<MudNavGroup Title="Inventory">
<MudNavGroup Title="System">
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Os.DetailsHref(Id) Match="NavLinkMatch.Prefix">Os</MudNavLink>
<MudNavGroup Title="Updates">
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Updates.Installed.IndexHref(Id) Match="NavLinkMatch.Prefix">Installed</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Updates.Pending.IndexHref(Id) Match="NavLinkMatch.Prefix">Pending</MudNavLink>
</MudNavGroup>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Sessions.IndexHref(Id) Match="NavLinkMatch.Prefix">Sessions</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Software.IndexHref(Id) Match="NavLinkMatch.Prefix">Software</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Services.IndexHref(Id) Match="NavLinkMatch.Prefix">Services</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Printers.IndexHref(Id) Match="NavLinkMatch.Prefix">Printers</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Volumes.IndexHref(Id) Match="NavLinkMatch.Prefix">Volumes</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Users.IndexHref(Id) Match="NavLinkMatch.Prefix">Users</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.Groups.IndexHref(Id) Match="NavLinkMatch.Prefix">Groups</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.StoragePools.IndexHref(Id) Match="NavLinkMatch.Prefix">Storage Pools</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Systems.VirtualMaschines.IndexHref(Id) Match="NavLinkMatch.Prefix">Virtual Maschines</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="Network">
<MudNavLink Href=@Navigation.Management.Hosts.Network.Interfaces.IndexHref(Id) Match="NavLinkMatch.Prefix">Interfaces</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Network.Addresses.IndexHref(Id) Match="NavLinkMatch.Prefix">Addresses</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Network.Nameservers.IndexHref(Id) Match="NavLinkMatch.Prefix">Nameservers</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Network.Gateways.IndexHref(Id) Match="NavLinkMatch.Prefix">Gateways</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Network.Routes.IndexHref(Id) Match="NavLinkMatch.Prefix">Routes</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="Hardware">
<MudNavLink Href=@Navigation.Management.Hosts.Hardware.Mainboard.DetailsHref(Id) Match="NavLinkMatch.Prefix">Mainboard</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Hardware.Processors.DetailsHref(Id) Match="NavLinkMatch.Prefix">Processors</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Hardware.Memory.IndexHref(Id) Match="NavLinkMatch.Prefix">Memory</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Hardware.Drives.IndexHref(Id) Match="NavLinkMatch.Prefix">Drives</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Hardware.Videocards.IndexHref(Id) Match="NavLinkMatch.Prefix">Videocards</MudNavLink>
</MudNavGroup>
</MudNavGroup>
</div>
<div class="mt-1">
<MudNavLink Href=@Navigation.Management.Hosts.LogsHref(Id) Match="NavLinkMatch.Prefix">Logs</MudNavLink>
</div>

View file

@ -0,0 +1,69 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Driver;
using Vaitr.Bus;
namespace Insight.Web.Components.Navbars;
public partial class Host : IDisposable
{
[CascadingParameter] public IReadOnlyDictionary<string, object>? RouteValues { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private Bus Bus { get; init; } = default!;
private CustomerEntity? CustomerEntity { get; set; }
private HostEntity HostEntity { get; set; }
private string? Id { get; set; }
private IDisposable? Token { get; set; }
public bool Disposed { get; set; } = false;
protected override void OnInitialized()
{
Token = Bus?.Subscribe<string>(OnRefresh, p => p == Events.Layout.Rendered);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (RouteValues is not null && Id is null)
{
if (RouteValues.TryGetValue("hostId", out object? rawId) == false) return;
Id = rawId?.ToString();
HostEntity = await Database.Host()
.Find(p => p.Id == Id)
.FirstOrDefaultAsync(default);
if (HostEntity is not null)
{
CustomerEntity = await Database.Customer()
.Find(p => p.Id == HostEntity.Customer)
.FirstOrDefaultAsync(default);
}
await InvokeAsync(() => StateHasChanged());
}
}
public void OnRefresh(string? message)
{
InvokeAsync(() => StateHasChanged());
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed) return;
if (disposing is false) return;
Token?.Dispose();
}
}

View file

@ -0,0 +1,112 @@
@inherits ComponentBase
<MudNavGroup Title="Monitoring" Expanded="ExpandMonitoringGroup">
@*<MudNavLink Href=@Navigation.Monitoring.Index Match="NavLinkMatch.Prefix">
Dashboard
</MudNavLink>*@
<MudNavLink Href=@Navigation.Monitoring.Maintenance.Index Match="NavLinkMatch.Prefix">
Maintenance
</MudNavLink>
@*<MudNavLink Href=@Navigation.Monitoring.Problems Match="NavLinkMatch.Prefix">
Problems
</MudNavLink>*@
</MudNavGroup>
<MudNavGroup Title="Inventory" Expanded="ExpandInventoryGroup">
<MudNavGroup Title="System" Expanded="ExpandInventorySystem">
<MudNavLink Href=@Navigation.Inventory.Systems.Os.Index Match="NavLinkMatch.Prefix">
Os
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Updates.Index Match="NavLinkMatch.Prefix">
Updates
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Software.Index Match="NavLinkMatch.Prefix">
Software
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Services.Index Match="NavLinkMatch.Prefix">
Services
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Sessions.Index Match="NavLinkMatch.Prefix">
Sessions
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Printers.Index Match="NavLinkMatch.Prefix">
Printers
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Volumes.Index Match="NavLinkMatch.Prefix">
Volumes
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Users.Index Match="NavLinkMatch.Prefix">
Users
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.Groups.Index Match="NavLinkMatch.Prefix">
Groups
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.StoragePools.Index Match="NavLinkMatch.Prefix">
Storage Pools
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Systems.VirtualMaschines.Index Match="NavLinkMatch.Prefix">
Virtual Maschines
</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="Network" Expanded="ExpandInventoryNetwork">
<MudNavLink Href=@Navigation.Inventory.Network.Interfaces.Index Match="NavLinkMatch.Prefix">
Interfaces
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Network.Addresses.Index Match="NavLinkMatch.Prefix">
Addresses
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Network.Nameservers.Index Match="NavLinkMatch.Prefix">
Nameservers
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Network.Gateways.Index Match="NavLinkMatch.Prefix">
Gateways
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Network.Routes.Index Match="NavLinkMatch.Prefix">
Routes
</MudNavLink>
</MudNavGroup>
<MudNavGroup Title="Hardware" Expanded="ExpandInventoryHardware">
<MudNavLink Href=@Navigation.Inventory.Hardware.Mainboards.Index Match="NavLinkMatch.Prefix">
Mainboards
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Hardware.Processors.Index Match="NavLinkMatch.Prefix">
Processors
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Hardware.Memory.Index Match="NavLinkMatch.Prefix">
Memory
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Hardware.Drives.Index Match="NavLinkMatch.Prefix">
Drives
</MudNavLink>
<MudNavLink Href=@Navigation.Inventory.Hardware.Videocards.Index Match="NavLinkMatch.Prefix">
Videocards
</MudNavLink>
</MudNavGroup>
</MudNavGroup>
<MudNavGroup Title="Management" Expanded="ExpandManagementGroup">
<MudNavLink Href=@Navigation.Management.Overview.Index Match="NavLinkMatch.Prefix">
Overview
</MudNavLink>
<MudNavLink Href=@Navigation.Management.Accounts.Index Match="NavLinkMatch.Prefix">
Accounts
</MudNavLink>
<MudNavLink Href=@Navigation.Management.Customers.Index Match="NavLinkMatch.Prefix">
Customers
</MudNavLink>
<MudNavLink Href=@Navigation.Management.Hosts.Index Match="NavLinkMatch.Prefix">
Hosts
</MudNavLink>
@*<MudNavLink Href=@Navigation.Management.HostGroups.Index Match="NavLinkMatch.Prefix">
Host Groups
</MudNavLink>*@
<MudNavLink Href=@Navigation.Management.Agents.Index Match="NavLinkMatch.Prefix">
Agents
</MudNavLink>
</MudNavGroup>
@*<MudNavGroup Title="Test">
<MudNavLink Href="chat" Match="NavLinkMatch.Prefix">
Chat
</MudNavLink>
</MudNavGroup>*@

View file

@ -0,0 +1,136 @@
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using Vaitr.Bus;
namespace Insight.Web.Components.Navbars;
public partial class Main : IDisposable
{
[Inject] private Bus Bus { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private string Uri { get; set; } = string.Empty;
private IDisposable? Token { get; set; }
public bool Disposed { get; set; } = false;
protected override void OnInitialized()
{
Token = Bus?.Subscribe<string>(OnRefresh, p => p == Events.Layout.Rendered);
OnRefresh();
}
public void OnRefresh(string? message = null)
{
Uri = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
InvokeAsync(() => StateHasChanged());
}
private bool ExpandMonitoringGroup
{
get
{
if (Uri.StartsWith(Navigation.Monitoring.Index)) return true;
return false;
}
}
private bool ExpandManagementGroup
{
get
{
if (Uri.StartsWith(Navigation.Management.Overview.Index)) return true;
if (Uri.StartsWith(Navigation.Management.Accounts.Index)) return true;
if (Uri.StartsWith(Navigation.Management.Customers.Index)) return true;
if (Uri.StartsWith(Navigation.Management.Hosts.Index)) return true;
if (Uri.StartsWith(Navigation.Management.HostGroups.Index)) return true;
if (Uri.StartsWith(Navigation.Management.Agents.Index)) return true;
return false;
}
}
private bool ExpandInventoryGroup
{
get
{
if (ExpandInventorySystem) return true;
if (ExpandInventoryNetwork) return true;
if (ExpandInventoryHardware) return true;
return false;
}
}
private bool ExpandInventorySystem
{
get
{
if (Uri.StartsWith(Navigation.Inventory.Systems.Os.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Os.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Os.Guests)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Updates.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Updates.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Software.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Software.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Services.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Services.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Sessions.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Printers.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Volumes.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Users.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Users.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Groups.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.Groups.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.StoragePools.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Systems.VirtualMaschines.Index)) return true;
return false;
}
}
private bool ExpandInventoryNetwork
{
get
{
if (Uri.StartsWith(Navigation.Inventory.Network.Interfaces.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Addresses.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Addresses.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Nameservers.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Nameservers.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Gateways.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Gateways.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Routes.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Network.Routes.Hosts)) return true;
return false;
}
}
private bool ExpandInventoryHardware
{
get
{
if (Uri.StartsWith(Navigation.Inventory.Hardware.Mainboards.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Mainboards.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Processors.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Processors.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Memory.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Memory.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Drives.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Drives.Hosts)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Videocards.Index)) return true;
if (Uri.StartsWith(Navigation.Inventory.Hardware.Videocards.Hosts)) return true;
return false;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed) return;
if (disposing is false) return;
Token?.Dispose();
}
}

View file

@ -0,0 +1,29 @@
@using System.Reflection;
@inherits ComponentBase
<div class="mt-5">
<MudNavMenu>
<CascadingValue Value="RouteValues">
@if (_content == Content.Account)
{
<Account />
}
else if (_content == Content.Host)
{
<Host />
}
else if (_content == Content.Customer)
{
<Customer />
}
else
{
<Main />
}
</CascadingValue>
</MudNavMenu>
</div>
<MudText Typo="Typo.subtitle1" Class="d-flex flex-wrap flex-grow-1 align-content-end justify-center mb-2" Style="opacity: 0.2;">
@(Assembly.GetEntryAssembly()?.GetName().Version)
</MudText>

View file

@ -0,0 +1,59 @@
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using Vaitr.Bus;
namespace Insight.Web.Components.Navbars;
public partial class NavSwitch : IDisposable
{
[CascadingParameter] public IReadOnlyDictionary<string, object>? RouteValues { get; set; }
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
[Inject] private Bus Bus { get; init; } = default!;
public string Url { get; set; } = string.Empty;
private IDisposable? Token { get; set; }
public bool Disposed { get; set; } = false;
private enum Content { Main, Account, Customer, Host }
private Content _content = Content.Main;
protected override void OnInitialized()
{
Token = Bus?.SubscribeAsync<string>(OnRefreshAsync, p => p == Events.Layout.Rendered);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await OnRefreshAsync().ConfigureAwait(false);
}
}
public async ValueTask OnRefreshAsync(string? message = null, CancellationToken cancellationToken = default)
{
Url = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
if (Url is null) return;
if (Url.StartsWith($"{Navigation.Account.Index}/")) _content = Content.Account;
else if (Url.StartsWith($"{Navigation.Management.Customers.Index}/")) _content = Content.Customer;
else if (Url.StartsWith($"{Navigation.Management.Hosts.Index}/")) _content = Content.Host;
else _content = Content.Main;
await InvokeAsync(StateHasChanged).ConfigureAwait(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (Disposed) return;
Token?.Dispose();
}
}

View file

@ -0,0 +1,49 @@
@using System.Collections.Concurrent;
@using Vaitr.Bus;
@using static Insight.Web.Constants.Events.Chat;
@inject Bus Bus
@inject SessionHandler SessionHandler
@implements IDisposable
<AuthorizeView>
<Authorized>
<MudIconButton OnClick="()=>_chatDialog?.Toggle()" Icon="@(_chatDialog?.NewMessages > 0 ? Icons.Material.Filled.MarkUnreadChatAlt : Icons.Material.Filled.Chat)" Color="Color.Inherit" />
<ChatDialog @ref="_chatDialog" />
</Authorized>
</AuthorizeView>
@code {
private ChatDialog? _chatDialog;
private bool _disposed;
private readonly ConcurrentBag<IDisposable> _subscriptions = new();
protected override async Task OnInitializedAsync()
{
_subscriptions.Add(Bus.SubscribeAsync<string>(async (x, c) => await InvokeAsync(StateHasChanged), p => p == Events.Sessions.Changed));
_subscriptions.Add(Bus.SubscribeAsync<ChatMessageReceived>(async (x, c) => await InvokeAsync(StateHasChanged), p => p.Message.SenderId != SessionHandler.State.Uid));
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// auto disposed when starved circuit timeouts (disconnects)
if (_disposed is false)
{
if (disposing)
{
foreach (var sub in _subscriptions) sub.Dispose();
_subscriptions.Clear();
}
_disposed = true;
}
}
}

View file

@ -0,0 +1,17 @@
<CascadingValue Value="RouteValues">
<MudDrawer @bind-Open="@_open" ClipMode="DrawerClipMode.Docked" Variant="DrawerVariant.Responsive" Breakpoint="Breakpoint.Md" Elevation="2">
<AuthorizeView>
<Authorized>
<NavSwitch />
</Authorized>
</AuthorizeView>
</MudDrawer>
</CascadingValue>
@code {
[CascadingParameter] public IReadOnlyDictionary<string, object>? RouteValues { get; set; }
private bool _open = true;
public void Toggle() => _open = !_open;
}

View file

@ -0,0 +1,70 @@
@inject NavigationManager NavigationManager
<AuthorizeView>
<Authorized>
<MudMenu @ref="_menu" ActivationEvent="@MouseEvent.LeftClick" Dense="true" Class="ml-4" AnchorOrigin="Origin.TopRight" TransformOrigin="Origin.TopRight">
<ActivatorContent>
@if (string.IsNullOrEmpty(""))
{
<MudAvatar Square Variant="Variant.Filled" Color="Color.Dark">
@context.User.Identity?.Name?.FirstOrDefault()
</MudAvatar>
}
else
{
<MudImage Src="" />
}
</ActivatorContent>
<ChildContent>
<MudCard Elevation="0" Square="true" Class="mt-n2">
<MudCardHeader>
<CardHeaderAvatar>
@if (string.IsNullOrEmpty(""))
{
<MudAvatar Square Variant="Variant.Filled" Color="Color.Dark">
@context.User.Identity?.Name?.FirstOrDefault()
</MudAvatar>
}
else
{
<MudAvatar Image="" />
}
</CardHeaderAvatar>
<CardHeaderContent>
<MudText Typo="Typo.body2">@context.User.Identity?.Name</MudText>
@*<MudText Typo="Typo.caption">@context.User.Identity?.Name!</MudText>*@
</CardHeaderContent>
</MudCardHeader>
</MudCard>
@*<MudDivider Class="mb-2" />*@
<MudListItem Text="Profile" OnClick="@(s=>OnProfile())" Icon="@Icons.Material.Outlined.Person" />
<MudListItem Text="Logout" OnClick="@(s=>OnLogout())" Icon="@Icons.Material.Filled.Logout" />
</ChildContent>
</MudMenu>
</Authorized>
<NotAuthorized>
<MudIconButton Icon="@Icons.Material.Outlined.Login" Color="Color.Inherit" Class="ml-3" OnClick="@(s=>OnLogin())" />
</NotAuthorized>
</AuthorizeView>
@code{
private MudMenu? _menu;
private void OnLogin()
{
_menu?.CloseMenu();
NavigationManager.NavigateTo(Navigation.Account.Login, false);
}
private void OnLogout()
{
_menu?.CloseMenu();
NavigationManager.NavigateTo(Navigation.Account.Logout, true);
}
private void OnProfile()
{
_menu?.CloseMenu();
NavigationManager.NavigateTo(Navigation.Account.Profile, false);
}
}

View file

@ -0,0 +1,14 @@
@using Vaitr.Bus;
@inject SessionHandler SessionHandler
@inject Bus Bus
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await SessionHandler.UpdateStateAsync(default);
}
}
}

View file

@ -0,0 +1,90 @@
@using Blazored.LocalStorage;
@using Microsoft.AspNetCore.Identity;
@using MongoDB.Driver;
@using Insight.Infrastructure
@inject IServiceScopeFactory ServiceScopeFactory
@inject ILocalStorageService LocalStorageService
@inject IMongoDatabase Database
@inject AuthenticationStateProvider AuthenticationStateProvider
<MudThemeProvider Theme="CurrentTheme" IsDarkMode="DarkMode" />
@if (DisableIcon is false)
{
<MudIconButton OnClick="@OnDarkModeToggleAsync" Icon="@(DarkMode ? Icons.Material.Filled.Brightness5 : Icons.Material.Filled.Brightness4)" Color="Color.Inherit" />
}
@code {
[Parameter] public bool DisableIcon { get; set; }
private MudTheme CurrentTheme { get; } = Themes.Default();
private bool DarkMode { get; set; }
protected override async Task OnInitializedAsync()
{
await LoadDarkModeAsync();
}
public async Task LoadDarkModeAsync()
{
// local storage
var storageDarkMode = await LocalStorageService.GetItemAsync<bool?>("darkmode");
if (storageDarkMode is bool darkmodeValue) DarkMode = darkmodeValue;
// database override
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (state?.User.Identity is not null && state.User.Identity.IsAuthenticated)
{
using var scope = ServiceScopeFactory.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<InsightUser>>();
if (await userManager.FindByNameAsync(state.User.Identity.Name) is not InsightUser user) return;
var userPrefs = await Database.UserPreference()
.Find(p => p.User == user.Id.ToString())
.FirstOrDefaultAsync();
if (userPrefs is null) return;
DarkMode = userPrefs.DarkMode;
await LocalStorageService.SetItemAsync("darkmode", DarkMode);
}
}
private async Task OnDarkModeToggleAsync()
{
// update current
DarkMode = !DarkMode;
// update local storage
await LocalStorageService.SetItemAsync("darkmode", DarkMode);
// update database
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (state?.User.Identity is not null && state.User.Identity.IsAuthenticated)
{
using var scope = ServiceScopeFactory.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<InsightUser>>();
if (await userManager.FindByNameAsync(state.User.Identity.Name) is not InsightUser user) return;
var date = DateTime.Now;
var userPrefs = await Database.UserPreference()
.UpdateOneAsync(p => p.User == user.Id.ToString(), Builders<InsightUserPreferences>.Update
.SetOnInsert(p => p.User, user.Id.ToString())
.SetOnInsert(p => p.Insert, date)
.Set(p => p.Update, date)
.Set(p => p.DarkMode, DarkMode), new UpdateOptions
{
IsUpsert = true
});
}
await InvokeAsync(StateHasChanged);
}
}

View file

@ -0,0 +1,24 @@
using Insight.Web.Models;
namespace Insight.Web.Constants;
public static class Events
{
public static class Sessions
{
public const string Changed = "sessions.changed";
}
public static class Layout
{
public const string Rendered = "layout.rendered";
}
public static class Chat
{
public record ChatUserConnected(ChatUser User);
public record ChatUserDisconnected(ChatUser User);
public record ChatRefresh();
public record ChatMessageReceived(ChatSession Session, ChatMessage Message);
}
}

View file

@ -0,0 +1,7 @@
namespace Insight.Web.Constants;
public static class Global
{
public const string Name = "Insight";
public const string NotFound = "Not FoundInsight";
}

View file

@ -0,0 +1,747 @@
namespace Insight.Web.Constants;
public static class Navigation
{
public const string Home = "";
public static class Internal
{
public const string Sessions = "internal/sessions";
public const string Seed = "internal/seed";
}
public static class Account
{
public const string Index = "account";
public const string Login = "account/login";
public const string LoginTFA = "account/login/{key:guid}";
public const string SignIn = "account/signin";
public const string SignInTFA = "account/signin/2fa";
public const string Logout = "account/logout";
public const string Lockout = "account/lockout";
public const string Profile = "account/profile";
public const string ChangePassword = "account/changepassword";
public static string LoginHref(string redirect)
{
if (string.IsNullOrWhiteSpace(redirect))
{
return Login;
}
return $"{Login}?redirect={redirect}";
}
public static string LoginTFAHref(Guid key)
{
return LoginTFA.Replace("{key:guid}", key.ToString());
}
public static string SignInHref(Guid key)
{
return $"{SignIn}?key={key}";
}
public static string SignInTFAHref(Guid key)
{
return $"{SignInTFA}?key={key}";
}
public static string ChangePasswordHref(Guid key)
{
return $"{ChangePassword}?key={key}";
}
}
public static class Monitoring
{
public const string Index = "monitoring";
public static class Maintenance
{
public const string Index = "monitoring/maintenance";
public static class Drives
{
public const string Index = "monitoring/maintenance/drives";
}
public static class StoragePools
{
public const string Index = "monitoring/maintenance/storagepools";
}
public static class Volumes
{
public const string Index = "monitoring/maintenance/volumes";
}
public static class Guests
{
public const string Index = "monitoring/maintenance/guests";
}
public static class Snapshots
{
public const string Index = "monitoring/maintenance/snapshots";
}
public static class Updates
{
public const string Index = "monitoring/maintenance/updates";
}
}
}
public static class Management
{
public const string Index = "management";
public static class Overview
{
public const string Index = "management/overview";
}
public static class Accounts
{
public const string Index = "management/accounts";
public const string Details = "management/accounts/{accountId}";
public static string DetailsHref(string? accountId)
{
return Details.Replace("{accountId}", accountId);
}
}
public static class Customers
{
public const string Index = "management/customers";
public const string Details = "management/customers/{customerId}";
public const string Hosts = "management/customers/{customerId}/hosts";
public const string HostsAssign = "management/customers/{customerId}/hosts/assign";
public static string DetailsHref(string? customerId)
{
return Details.Replace("{customerId}", customerId);
}
public static string HostsHref(string? customerId)
{
return Hosts.Replace("{customerId}", customerId);
}
public static string HostsAssignHref(string? customerId)
{
return HostsAssign.Replace("{customerId}", customerId);
}
}
public static class Agents
{
public const string Index = "management/agents";
public const string Details = "management/agents/{agentId}";
public const string Logs = "management/agents/{agentId}/logs";
public const string HostAssign = "management/agents/{agentId}/assign";
public static string DetailsHref(string? agentId)
{
return Details.Replace("{agentId}", agentId);
}
public static string LogsHref(string? agentId)
{
return Logs.Replace("{agentId}", agentId);
}
public static string HostAssingHref(string? agentId)
{
return HostAssign.Replace("{agentId}", agentId);
}
}
public static class Hosts
{
public const string Index = "management/hosts";
public const string Details = "management/hosts/{hostId}";
public const string Logs = "management/hosts/{hostId}/logs";
public const string CustomerAssign = "management/hosts/{hostId}/customer/assign";
public const string AgentAssign = "management/hosts/{hostId}/agent/assign";
public static string DetailsHref(string? hostId)
{
return Details.Replace("{hostId}", hostId);
}
public static string LogsHref(string? hostId)
{
return Logs.Replace("{hostId}", hostId);
}
public static string CustomerAssingHref(string? hostId)
{
return CustomerAssign.Replace("{hostId}", hostId);
}
public static string AgentAssingHref(string? hostId)
{
return AgentAssign.Replace("{hostId}", hostId);
}
public static class Actions
{
public static class Console
{
public const string Index = "management/hosts/{hostId}/console";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
}
public static class Systems
{
public static class Os
{
public const string Details = "management/hosts/{hostId}/os";
public static string DetailsHref(string? hostId)
{
return Details.Replace("{hostId}", hostId);
}
}
public static class Updates
{
public static class Installed
{
public const string Index = "management/hosts/{hostId}/updates/installed";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Pending
{
public const string Index = "management/hosts/{hostId}/updates/pending";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
}
public static class Sessions
{
public const string Index = "management/hosts/{hostId}/sessions";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Software
{
public const string Index = "management/hosts/{hostId}/software";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Services
{
public const string Index = "management/hosts/{hostId}/services";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Printers
{
public const string Index = "management/hosts/{hostId}/printers";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Volumes
{
public const string Index = "management/hosts/{hostId}/volumes";
public const string Details = "management/hosts/{hostId}/volumes/{volumeId}";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
public static string DetailsHref(string? hostId, string? volumeId)
{
return Details.Replace("{hostId}", hostId).Replace("{volumeId}", volumeId);
}
}
public static class Users
{
public const string Index = "management/hosts/{hostId}/users";
public const string Details = "management/hosts/{hostId}/users/{userId}";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
public static string DetailsHref(string? hostId, string? userId)
{
return Details.Replace("{hostId}", hostId).Replace("{userId}", userId);
}
}
public static class Groups
{
public const string Index = "management/hosts/{hostId}/groups";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class StoragePools
{
public const string Index = "management/hosts/{hostId}/storagepools";
public const string Details = "management/hosts/{hostId}/storagepools/{storagePoolId}";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
public static string DetailsHref(string? hostId, string? storagePoolId)
{
return Details.Replace("{hostId}", hostId).Replace("{storagePoolId}", storagePoolId);
}
public static class VirtualDisks
{
public const string Index = "management/hosts/{hostId}/storagepools/{storagePoolId}/virtualdisks";
public const string Details = "management/hosts/{hostId}/storagepools/{storagePoolId}/virtualdisks/{virtualDiskId}";
public static string IndexHref(string? hostId, string? storagePoolId)
{
return Index.Replace("{hostId}", hostId).Replace("{storagePoolId}", storagePoolId);
}
public static string DetailsHref(string? hostId, string? storagePoolId, string? virtualDiskId)
{
return Details.Replace("{hostId}", hostId).Replace("{storagePoolId}", storagePoolId).Replace("{virtualDiskId}", virtualDiskId);
}
}
public static class PhysicalDisks
{
public const string Index = "management/hosts/{hostId}/storagepools/{storagePoolId}/physicaldisks";
public const string Details = "management/hosts/{hostId}/storagepools/{storagePoolId}/physicaldisks/{physicalDiskId}";
public static string IndexHref(string? hostId, string? storagePoolId)
{
return Index.Replace("{hostId}", hostId).Replace("{storagePoolId}", storagePoolId);
}
public static string DetailsHref(string? hostId, string? storagePoolId, string? physicalDiskId)
{
return Details.Replace("{hostId}", hostId).Replace("{storagePoolId}", storagePoolId).Replace("{physicalDiskId}", physicalDiskId);
}
}
}
public static class VirtualMaschines
{
public const string Index = "management/hosts/{hostId}/virtualmaschines";
public const string Details = "management/hosts/{hostId}/virtualmaschines/{virtualMaschineId}";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
public static string DetailsHref(string? hostId, string? virtualMaschineId)
{
return Details.Replace("{hostId}", hostId).Replace("{virtualMaschineId}", virtualMaschineId);
}
public static class Snapshots
{
public const string Details = "management/hosts/{hostId}/virtualmaschines/{virtualMaschineId}/snapshots/{snapshotId}";
public static string DetailsHref(string? hostId, string? virtualMaschineId, string? snapshotId)
{
return Details.Replace("{hostId}", hostId).Replace("{virtualMaschineId}", virtualMaschineId).Replace("{snapshotId}", snapshotId);
}
}
}
}
public static class Network
{
public static class Interfaces
{
public const string Index = "management/hosts/{hostId}/interfaces";
public const string Details = "management/hosts/{hostId}/interfaces/{interfaceId}";
public const string Addresses = "management/hosts/{hostId}/interfaces/{interfaceId}/addresses";
public const string Nameservers = "management/hosts/{hostId}/interfaces/{interfaceId}/nameservers";
public const string Gateways = "management/hosts/{hostId}/interfaces/{interfaceId}/gateways";
public const string Routes = "management/hosts/{hostId}/interfaces/{interfaceId}/routes";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
public static string DetailsHref(string? hostId, string? interfaceId)
{
return Details.Replace("{hostId}", hostId).Replace("{interfaceId}", interfaceId);
}
}
public static class Addresses
{
public const string Index = "management/hosts/{hostId}/addresses";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Gateways
{
public const string Index = "management/hosts/{hostId}/gateways";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Nameservers
{
public const string Index = "management/hosts/{hostId}/nameservers";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Routes
{
public const string Index = "management/hosts/{hostId}/routes";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
}
public static class Hardware
{
public static class Mainboard
{
public const string Details = "management/hosts/{hostId}/mainboard";
public static string DetailsHref(string? hostId)
{
return Details.Replace("{hostId}", hostId);
}
}
public static class Processors
{
public const string Details = "management/hosts/{hostId}/processors";
public static string DetailsHref(string? hostId)
{
return Details.Replace("{hostId}", hostId);
}
}
public static class Memory
{
public const string Index = "management/hosts/{hostId}/memory";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Drives
{
public const string Index = "management/hosts/{hostId}/drives";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
public static class Videocards
{
public const string Index = "management/hosts/{hostId}/videocards";
public static string IndexHref(string? hostId)
{
return Index.Replace("{hostId}", hostId);
}
}
}
}
public static class HostGroups
{
public const string Index = "management/hostgroups";
public const string Details = "management/hostgroups/{groupId}";
public static string DetailsHref(string? groupId)
{
return Details.Replace("{groupId}", groupId);
}
}
}
public static class Inventory
{
public static class Systems
{
public static class Os
{
public const string Index = "inventory/os";
public const string Hosts = "inventory/os/{osName}/hosts";
public const string Guests = "inventory/os/{osName}/guests";
public static string HostsHref(string? osName)
{
return Hosts.Replace("{osName}", osName);
}
public static string GuestsHref(string? osName)
{
return Guests.Replace("{osName}", osName);
}
}
public static class Updates
{
public const string Index = "inventory/updates";
public const string Hosts = "inventory/updates/{updateName}/hosts";
public static string HostsHref(string? updateName)
{
return Hosts.Replace("{updateName}", updateName);
}
}
public static class Software
{
public const string Index = "inventory/software";
public const string Hosts = "inventory/software/{softwareName}/hosts";
public static string HostsHref(string? software)
{
return Hosts.Replace("{softwareName}", software);
}
}
public static class Services
{
public const string Index = "inventory/services";
public const string Hosts = "inventory/services/{serviceName}/hosts";
public static string HostsHref(string? serviceName)
{
return Hosts.Replace("{serviceName}", serviceName);
}
}
public static class Sessions
{
public const string Index = "inventory/sessions";
}
public static class Printers
{
public const string Index = "inventory/printers";
}
public static class Volumes
{
public const string Index = "inventory/volumes";
}
public static class Users
{
public const string Index = "inventory/users";
public const string Hosts = "inventory/users/{userName}/hosts";
public static string HostsHref(string? userName)
{
return Hosts.Replace("{userName}", userName);
}
}
public static class Groups
{
public const string Index = "inventory/groups";
public const string Hosts = "inventory/groups/{groupName}/hosts";
public static string HostsHref(string? groupName)
{
return Hosts.Replace("{groupName}", groupName);
}
}
public static class StoragePools
{
public const string Index = "inventory/storagepools";
}
public static class VirtualMaschines
{
public const string Index = "inventory/virtualmaschines";
}
}
public static class Network
{
public static class Interfaces
{
public const string Index = "inventory/interfaces";
}
public static class Addresses
{
public const string Index = "inventory/addresses";
public const string Hosts = "inventory/addresses/{address}/hosts";
public static string HostsHref(string? address)
{
return Hosts.Replace("{address}", address);
}
}
public static class Nameservers
{
public const string Index = "inventory/nameservers";
public const string Hosts = "inventory/nameservers/{nameserverAddress}/hosts";
public static string HostsHref(string? nameserverAddress)
{
return Hosts.Replace("{nameserverAddress}", nameserverAddress);
}
}
public static class Gateways
{
public const string Index = "inventory/gateways";
public const string Hosts = "inventory/gateways/{gatewayAddress}/hosts";
public static string HostsHref(string? gatewayAddress)
{
return Hosts.Replace("{gatewayAddress}", gatewayAddress);
}
}
public static class Routes
{
public const string Index = "inventory/routes";
public const string Hosts = "inventory/routes/{routeAddress}/hosts";
public static string HostsHref(string? routeAddress)
{
return Hosts.Replace("{routeAddress}", routeAddress);
}
}
}
public static class Hardware
{
public static class Mainboards
{
public const string Index = "inventory/mainboards";
public const string Hosts = "inventory/mainboards/{mainboardName}/hosts";
public static string HostsHref(string? mainboardName)
{
return Hosts.Replace("{mainboardName}", mainboardName);
}
}
public static class Processors
{
public const string Index = "inventory/processors";
public const string Hosts = "inventory/processors/{processorName}/hosts";
public static string HostsHref(string? processorName)
{
return Hosts.Replace("{processorName}", processorName);
}
}
public static class Memory
{
public const string Index = "inventory/memory";
public const string Hosts = "inventory/memory/{memoryName}/hosts";
public static string HostsHref(string? memoryName)
{
return Hosts.Replace("{memoryName}", memoryName);
}
}
public static class Drives
{
public const string Index = "inventory/drives";
public const string Hosts = "inventory/drives/{driveName}/hosts";
public static string HostsHref(string? driveName)
{
return Hosts.Replace("{driveName}", driveName);
}
}
public static class Videocards
{
public const string Index = "inventory/videocards";
public const string Hosts = "inventory/videocards/{videocardName}/hosts";
public static string HostsHref(string? videocardName)
{
return Hosts.Replace("{videocardName}", videocardName);
}
}
}
}
public static class Communication
{
public const string Index = "communication";
public static class Chat
{
public const string Index = "communication/chat";
}
}
}

View file

@ -0,0 +1,39 @@
using MudBlazor;
namespace Insight.Web.Constants;
public static class Notification
{
public static class Default
{
public static void Options(SnackbarOptions options)
{
options.HideIcon = true;
options.ShowCloseIcon = false;
options.RequireInteraction = false;
options.ShowTransitionDuration = 0;
options.HideTransitionDuration = 1000;
options.VisibleStateDuration = 3000;
}
}
public static void Information(ISnackbar snackbar, string message, Action<SnackbarOptions>? options = null, string key = "")
{
snackbar.Add(message, Severity.Info, options ?? Default.Options, key);
}
public static void Success(ISnackbar snackbar, string? message = null, Action<SnackbarOptions>? options = null, string key = "")
{
snackbar.Add(message ?? "Success", Severity.Success, options ?? Default.Options, key);
}
public static void Warning(ISnackbar snackbar, string message, Action<SnackbarOptions>? options = null, string key = "")
{
snackbar.Add(message, Severity.Warning, options ?? Default.Options, key);
}
public static void Error(ISnackbar snackbar, string? message = null, Action<SnackbarOptions>? options = null, string key = "")
{
snackbar.Add(message ?? "Error", Severity.Error, options ?? Default.Options, key);
}
}

View file

@ -0,0 +1,186 @@
using MudBlazor;
namespace Insight.Web.Constants;
public static class Themes
{
public static MudTheme Default()
{
var theme = new MudTheme();
//theme.LayoutProperties.DrawerWidthRight = "300";
theme.Palette.Background = "#fafafa";
//theme.Palette.Surface = "#fafafa";
//theme.Palette.Surface = "#f6f6f6ff";
theme.PaletteDark.AppbarBackground = "#1f1f27";
theme.PaletteDark.DrawerBackground = "#24242d";
//theme.PaletteDark.DrawerBackground = "#32333dff";
theme.PaletteDark.DrawerText = "#ffffffb2";
theme.PaletteDark.Divider = "#ffffffb2";
theme.PaletteDark.DividerLight = "#ffffffb2";
//theme.PaletteDark.Surface = "#32333dff";
theme.PaletteDark.Primary = "#ffffff";
//theme.PaletteDark.TextPrimary = "#ffffff";
//Style = "background-color: #363740;"
return theme;
}
public static MudTheme ApplicationTheme() => new()
{
Palette = new PaletteLight
{
Primary = "#283593",
Black = "#0A0E19",
Success = "#64A70B",
Secondary = "#ff4081ff",
AppbarBackground = "rgba(255,255,255,0.8)",
AppbarText = "#424242",
BackgroundGrey = "#F9FAFC",
TextSecondary = "#425466",
Dark = "#110E2D",
DarkLighten = "#1A1643",
GrayDefault = "#4B5563",
GrayLight = "#9CA3AF",
GrayLighter = "#adbdccff"
},
PaletteDark = new PaletteLight
{
Primary = "#1A237E",
Black = "#27272f",
Background = "rgb(21,27,34)",
BackgroundGrey = "#27272f",
Surface = "#212B36",
DrawerBackground = "rgb(21,27,34)",
DrawerText = "rgba(255,255,255, 0.50)",
DrawerIcon = "rgba(255,255,255, 0.50)",
AppbarBackground = "rgba(21,27,34,0.7)",
AppbarText = "rgba(255,255,255, 0.70)",
TextPrimary = "rgba(255,255,255, 0.70)",
TextSecondary = "rgba(255,255,255, 0.50)",
ActionDefault = "#adadb1",
ActionDisabled = "rgba(255,255,255, 0.26)",
ActionDisabledBackground = "rgba(255,255,255, 0.12)",
DarkDarken = "rgba(21,27,34,0.7)",
Divider = "rgba(255,255,255, 0.12)",
DividerLight = "rgba(255,255,255, 0.06)",
TableLines = "rgba(255,255,255, 0.12)",
LinesDefault = "rgba(255,255,255, 0.12)",
LinesInputs = "rgba(255,255,255, 0.3)",
TextDisabled = "rgba(255,255,255, 0.2)"
},
LayoutProperties = new LayoutProperties
{
AppbarHeight = "80px",
DefaultBorderRadius = "6px",
},
Typography = new Typography
{
Default = new Default
{
FontSize = ".8125rem",
FontWeight = 400,
LineHeight = 1.43,
LetterSpacing = "normal",
FontFamily = new string[] { "Public Sans", "Roboto", "Arial", "sans-serif" }
},
H1 = new H1
{
FontSize = "4rem",
FontWeight = 700,
LineHeight = 1.167,
LetterSpacing = "-.01562em"
},
H2 = new H2
{
FontSize = "3.75rem",
FontWeight = 300,
LineHeight = 1.2,
LetterSpacing = "-.00833em"
},
H3 = new H3
{
FontSize = "3rem",
FontWeight = 600,
LineHeight = 1.167,
LetterSpacing = "0"
},
H4 = new H4
{
FontSize = "1.8rem",
FontWeight = 400,
LineHeight = 1.235,
LetterSpacing = ".00735em"
},
H5 = new H5
{
FontSize = "1.5rem",
FontWeight = 400,
LineHeight = 1.334,
LetterSpacing = "0"
},
H6 = new H6
{
FontSize = "1.125rem",
FontWeight = 600,
LineHeight = 1.6,
LetterSpacing = ".0075em"
},
Button = new Button
{
FontSize = ".8125rem",
FontWeight = 500,
LineHeight = 1.75,
LetterSpacing = ".02857em",
TextTransform = "uppercase"
},
Subtitle1 = new Subtitle1
{
FontSize = "1rem",
FontWeight = 400,
LineHeight = 1.75,
LetterSpacing = ".00938em"
},
Subtitle2 = new Subtitle2
{
FontSize = ".875rem",
FontWeight = 500,
LineHeight = 1.57,
LetterSpacing = ".00714em"
},
Body1 = new Body1
{
FontSize = "0.875rem",
FontWeight = 400,
LineHeight = 1.5,
LetterSpacing = ".00938em"
},
Body2 = new Body2
{
FontSize = ".8125rem",
FontWeight = 400,
LineHeight = 1.43,
LetterSpacing = ".01071em"
},
Caption = new Caption
{
FontSize = ".75rem",
FontWeight = 400,
LineHeight = 1.66,
LetterSpacing = ".03333em"
},
Overline = new Overline
{
FontSize = ".75rem",
FontWeight = 400,
LineHeight = 2.66,
LetterSpacing = ".08333em"
}
}
};
}

View file

@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using System.Web;
namespace Insight.Web.Extensions;
public static class NavigationManagerExtensions
{
public static Dictionary<string, StringValues> GetQueryString(this NavigationManager navigationManager)
{
var uri = navigationManager.ToAbsoluteUri(navigationManager.Uri);
if (uri is null) return new Dictionary<string, StringValues>();
return QueryHelpers.ParseQuery(uri.Query);
}
public static void ChangeQueryStringValue(this NavigationManager navigationManager, string key, string? value = null)
{
var queryString = GetQueryString(navigationManager);
if (queryString.ContainsKey(key))
{
if (queryString[key] == value) return;
queryString[key] = value;
}
else
{
if (value is null) return;
queryString.Add(key, value);
}
var uri = ReplaceQueryString(navigationManager.Uri, queryString);
navigationManager.NavigateTo(uri);
}
private static void ChangeQueryString(this NavigationManager navigationManager, Dictionary<string, StringValues> queryStrings)
{
var uri = ReplaceQueryString(navigationManager.Uri, queryStrings);
navigationManager.NavigateTo(uri);
}
private static string ReplaceQueryString(string uri, Dictionary<string, StringValues> queryString)
{
string url = uri;
foreach (var key in queryString.Keys)
{
url = RemoveQueryStringByKey(url, key);
}
var query = new Dictionary<string, string>();
foreach (var keys in queryString.Where(x => string.IsNullOrWhiteSpace(x.Value) is false))
{
query.Add(keys.Key, keys.Value.ToString());
}
return QueryHelpers.AddQueryString(url, query);
}
public static string ReplaceQueryStrings(string uri, Dictionary<string, string?> parameters)
{
string url = uri;
foreach (var key in parameters.Keys)
{
url = RemoveQueryStringByKey(url, key);
}
return QueryHelpers.AddQueryString(url, parameters);
}
private static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url);
// this gets all the query string key value pairs as a collection
var newQueryString = HttpUtility.ParseQueryString(uri.Query);
// this removes the key if exists
newQueryString.Remove(key);
// this gets the page path from root without QueryString
string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path);
return newQueryString.Count > 0
? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
: pagePathWithoutQueryString;
}
}

View file

@ -0,0 +1,45 @@
using Blazored.LocalStorage;
using Blazored.SessionStorage;
using Insight.Web.Services;
using Microsoft.AspNetCore.Components.Server.Circuits;
using MudBlazor.Services;
namespace Insight.Web.Hosting;
public static class ServiceExtensions
{
internal static IServiceCollection AddWebServices(this IServiceCollection services)
{
// HOSTS
services.AddHostedService<ServiceHost>();
// LOCAL STORAGE
services.AddBlazoredLocalStorage();
services.AddBlazoredSessionStorage();
// BLAZOR
services.AddHttpContextAccessor();
services.AddRazorPages();
services.AddServerSideBlazor(options =>
{
options.DetailedErrors = true;
});
// CIRCUIT
services.AddScoped<CircuitHandler>(p => p.GetRequiredService<SessionHandler>());
services.AddScoped<SessionHandler>();
services.AddSingleton<SessionPool>();
// CHAT
services.AddSingleton<ChatService>();
// MUDBLAZOR
services.AddMudServices(options =>
{
options.SnackbarConfiguration.PositionClass = MudBlazor.Defaults.Classes.Position.BottomLeft;
});
services.AddMudBlazorDialog();
return services;
}
}

View file

@ -0,0 +1,31 @@
namespace Insight.Web.Extensions;
public static class StringExtensions
{
public static string? Escape(this string? value)
{
if (value is null) return value;
return value.Replace("(", @"\(").Replace(")", @"\)").Replace(@"\", @"\\");
}
public static string? UriEscape(this string? value, bool revert = false)
{
if (value is null) return value;
if (revert)
{
value = value.Replace(@"$pc", @"%");
value = value.Replace(@"$fs", @"/");
value = value.Replace(@"$bs", @"\");
}
else
{
value = value.Replace(@"%", @"$pc");
value = value.Replace(@"/", @"$fs");
value = value.Replace(@"\", @"$bs");
}
return value;
}
}

View file

@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Product>Insight</Product>
<AssemblyName>web</AssemblyName>
<AssemblyVersion>2023.9.18.0</AssemblyVersion>
<RootNamespace>Insight.Web</RootNamespace>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishAot>false</PublishAot>
<PublishTrimmed>false</PublishTrimmed>
<!--<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>-->
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.1" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.4.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.11" />
<PackageReference Include="MudBlazor" Version="6.10.0" />
<PackageReference Include="Vaitr.Bus" Version="0.1.3" />
<!--Unix Serilog stuff-->
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" />
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
<PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
<PackageReference Include="System.Threading" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Agent\Insight.Agent.Assets\Insight.Agent.Assets.csproj" />
<ProjectReference Include="..\..\Core\Insight.Infrastructure\Insight.Infrastructure.csproj" />
<ProjectReference Include="..\Insight.Web.Assets\Insight.Web.Assets.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\media\" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,295 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Constants;
using Insight.Web.Models.Account;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using MongoDB.Driver;
using System.Collections.Concurrent;
namespace Insight.Web.Middleware;
public class IdentityMiddleware
{
public static IDictionary<Guid, LoginModel> Logins { get; private set; } = new ConcurrentDictionary<Guid, LoginModel>();
public static IDictionary<Guid, ChangePasswordModel> Passwords { get; private set; } = new ConcurrentDictionary<Guid, ChangePasswordModel>();
private RequestDelegate Next { get; }
private ILogger<IdentityMiddleware> Logger { get; }
public IdentityMiddleware(RequestDelegate next, ILogger<IdentityMiddleware> logger)
{
Next = next;
Logger = logger;
}
public async Task Invoke(HttpContext context, IServiceProvider serviceProvider)
{
try
{
// skip blazor / internal requests
if (context.Request.Path.Value.StartsWith("/_blazor")) return;
if (context.Request.Path.Value.StartsWith("/_framework")) return;
if (context.Request.Path.Value.StartsWith("/_content")) return;
if (context.Request.Path.Value.StartsWith("/css")) return;
if (context.Request.Path.Value.StartsWith("/fonts")) return;
if (context.Request.Path.Value.StartsWith("/media")) return;
if (context.Request.Path.Value.StartsWith("/js")) return;
if (context.Request.Path.Value.StartsWith("/favicon")) return;
//Logger.LogWarning($"{context.Request.Path}");
// skip hub request (test)
if (context.Request.Path.Value.StartsWith("/hub")) return;
// ignore 2fa login request
if (context.Request.Path.Value.StartsWith($"/{Navigation.Account.LoginTFA}")) return;
// ignore login request
if (context.Request.Path.Value.StartsWith($"/{Navigation.Account.Login}")) return;
// 2fa signin request
if (context.Request.Path.Value.StartsWith($"/{Navigation.Account.SignInTFA}"))
{
await OnSignInTFAAsync(context, serviceProvider).ConfigureAwait(false);
return;
}
// signin request
if (context.Request.Path.Value.StartsWith($"/{Navigation.Account.SignIn}"))
{
await OnSignInAsync(context, serviceProvider).ConfigureAwait(false);
return;
}
// logout request
if (context.Request.Path.Value.StartsWith($"/{Navigation.Account.Logout}"))
{
await OnLogoutAsync(context, serviceProvider).ConfigureAwait(false);
return;
}
//password change request
if (context.Request.Path.Value.StartsWith($"/{Navigation.Account.ChangePassword}"))
{
await OnChangePasswordAsync(context, serviceProvider).ConfigureAwait(false);
return;
}
await OnCatchRoute(context, serviceProvider).ConfigureAwait(false);
}
finally
{
await Next.Invoke(context).ConfigureAwait(false);
}
}
private async ValueTask OnSignInAsync(HttpContext context, IServiceProvider serviceProvider)
{
if (context.Request.Query.ContainsKey("key") is false) return;
var key = Guid.Parse(context.Request.Query["key"]);
try
{
using var scope = serviceProvider.CreateScope();
var database = scope.ServiceProvider.GetRequiredService<IMongoDatabase>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<InsightUser>>();
var signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<InsightUser>>();
if (await userManager.FindByEmailAsync(Logins[key].Email) is not InsightUser user)
{
context.Response.Redirect($"/{Navigation.Account.LoginHref(Logins[key].Redirect)}");
return;
}
if (await userManager.CheckPasswordAsync(user, Logins[key].Password) is false)
{
context.Response.Redirect($"/{Navigation.Account.LoginHref(Logins[key].Redirect)}");
return;
}
if (await userManager.GetTwoFactorEnabledAsync(user))
{
var result = await signInManager.PasswordSignInAsync(user.UserName, Logins[key].Password, Logins[key].RememberMe, lockoutOnFailure: false).ConfigureAwait(false);
context.Response.Redirect($"/{Navigation.Account.LoginTFAHref(key)}");
return;
}
if (string.IsNullOrWhiteSpace(Logins[key].Redirect) is false)
{
context.Response.Redirect($"/{Logins[key].Redirect}");
}
else
{
context.Response.Redirect($"/{Navigation.Home}");
}
await signInManager.SignInAsync(user, new AuthenticationProperties
{
AllowRefresh = true,
IsPersistent = Logins[key].RememberMe,
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(8)
}).ConfigureAwait(false);
await database.UserLog()
.InsertOneAsync(new InsightUserLogEntity
{
Insert = DateTime.Now,
User = user.Id.ToString(),
Timestamp = DateTime.Now,
Message = $"Login ({context.Request.Host})",
}, cancellationToken: default)
.ConfigureAwait(false);
Logins.Remove(key);
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
finally
{
//Logger.LogInformation("redirect {0}", string.Concat(context.Response.Headers));
}
}
private async ValueTask OnSignInTFAAsync(HttpContext context, IServiceProvider serviceProvider)
{
Logger.LogInformation($"OnSignInTFAAsync ({context.Request.Path})");
if (context.Request.Query.ContainsKey("key") is false) return;
var key = Guid.Parse(context.Request.Query["key"]);
try
{
using var scope = serviceProvider.CreateScope();
var database = scope.ServiceProvider.GetRequiredService<IMongoDatabase>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<InsightUser>>();
var signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<InsightUser>>();
var user = await userManager.FindByEmailAsync(Logins[key].Email).ConfigureAwait(false);
var authenticatorCode = Logins[key].TwoFactorToken.Replace(" ", string.Empty).Replace("-", string.Empty);
var valid = await userManager.VerifyTwoFactorTokenAsync(user, userManager.Options.Tokens.AuthenticatorTokenProvider, authenticatorCode).ConfigureAwait(false);
if (valid is false)
{
context.Response.Redirect($"/{Navigation.Account.LoginTFAHref(key)}");
return;
}
var result = await signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, true, Logins[key].RememberMe).ConfigureAwait(false);
if (result.IsNotAllowed) return;
if (result.IsLockedOut) return;
if (result.Succeeded)
{
if (string.IsNullOrWhiteSpace(Logins[key].Redirect) is false)
{
context.Response.Redirect($"/{Logins[key].Redirect}");
}
else
{
context.Response.Redirect($"/{Navigation.Home}");
}
await database.UserLog()
.InsertOneAsync(new InsightUserLogEntity
{
Insert = DateTime.Now,
User = user.Id.ToString(),
Timestamp = DateTime.Now,
Message = $"Login 2FA ({context.Request.Host})",
}, cancellationToken: default)
.ConfigureAwait(false);
}
else
{
context.Response.Redirect($"/{Navigation.Account.LoginHref(Logins[key].Redirect)}");
}
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
finally
{
Logins.Remove(key);
}
}
private async ValueTask OnLogoutAsync(HttpContext context, IServiceProvider serviceProvider)
{
Logger.LogInformation($"OnLogoutAsync ({context.Request.Path})");
using var scope = serviceProvider.CreateScope();
var signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<InsightUser>>();
await signInManager.SignOutAsync().ConfigureAwait(false);
context.Response.Redirect($"/");
}
private async ValueTask OnDisconnectAsync(HttpContext context, IServiceProvider serviceProvider)
{
Logger.LogInformation($"OnDisconnectAsync ({context.Request.Path})");
using var scope = serviceProvider.CreateScope();
var signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<InsightUser>>();
await signInManager.SignOutAsync().ConfigureAwait(false);
context.Abort();
}
private async ValueTask OnChangePasswordAsync(HttpContext context, IServiceProvider serviceProvider)
{
Logger.LogInformation($"OnChangePasswordAsync ({context.Request.Path})");
if (context.Request.Query.ContainsKey("key") is false) return;
var key = Guid.Parse(context.Request.Query["key"]);
try
{
using var scope = serviceProvider.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<InsightUser>>();
var user = await userManager.GetUserAsync(context.User).ConfigureAwait(false);
var result = await userManager.ChangePasswordAsync(user, Passwords[key]?.OldPassword, Passwords[key]?.NewPassword).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
finally
{
Passwords.Remove(key);
context.Response.Redirect($"/{Navigation.Account.Profile}");
}
}
private ValueTask OnCatchRoute(HttpContext context, IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<InsightUser>>();
//check authentication
var authenticated = signInManager.IsSignedIn(context.User);
if (authenticated) return default;
Logger.LogCritical("non auth - redirect");
var returnPath = context.Request.Path.Value ?? string.Empty;
if (returnPath.StartsWith("/"))
{
returnPath = returnPath.Substring(1);
}
context.Response.Redirect($"/{Navigation.Account.LoginHref(returnPath)}");
return default;
}
}

View file

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Insight.Web.Models.Account;
public class ChangePasswordModel
{
[Required]
[DataType(DataType.Password)]
public string? OldPassword { get; set; }
[Required]
[DataType(DataType.Password)]
public string? NewPassword { get; set; }
[Required]
[DataType(DataType.Password)]
public string? ConfirmNewPassword { get; set; }
}

View file

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Insight.Web.Models.Account;
public class LoginModel
{
public Guid Key { get; } = Guid.NewGuid();
//[EmailAddress, Required]
public string? Email { get; set; }
//[DataType(DataType.Password), Required]
public string? Password { get; set; }
public string? TwoFactorToken { get; set; }
public bool RememberMe { get; set; }
public string? Redirect { get; set; }
}

View file

@ -0,0 +1,12 @@
using MongoDB.Bson;
namespace Insight.Web.Models;
public class ChatMessage
{
public ObjectId Id { get; } = ObjectId.GenerateNewId();
public DateTime CreatedDate { get; } = DateTime.Now;
public ObjectId? SenderId { get; set; }
public string? Message { get; set; }
}

View file

@ -0,0 +1,32 @@
using MongoDB.Bson;
using System.Collections.Concurrent;
namespace Insight.Web.Models;
public class ChatSession
{
public ObjectId Id { get; } = ObjectId.GenerateNewId();
public IEnumerable<ChatUser> Members { get; }
public ConcurrentBag<ChatMessage> Messages { get; } = new();
private readonly Func<ChatSession, ChatMessage, CancellationToken, Task> OnMessageSent;
public ChatSession(IEnumerable<ChatUser> members, Func<ChatSession, ChatMessage, CancellationToken, Task> onMessageSent)
{
Members = members;
OnMessageSent = onMessageSent;
}
public async Task SendMessage(ChatUser sender, string message, CancellationToken cancellationToken)
{
var cm = new ChatMessage
{
SenderId = sender.Uid,
Message = message
};
Messages.Add(cm);
await OnMessageSent(this, cm, cancellationToken);
}
}

View file

@ -0,0 +1,30 @@
using MongoDB.Bson;
namespace Insight.Web.Models;
public class ChatUser
{
public ObjectId Uid { get; set; }
public bool Online { get; set; }
public string? Username { get; set; }
public string? Email { get; set; }
public byte[]? Avatar { get; set; }
public ChatUser(ObjectId uid)
{
Uid = uid;
}
public override bool Equals(object? obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
if (obj is ChatUser user && Uid != user.Uid) return false;
return true;
}
public override int GetHashCode()
{
return Uid.GetHashCode();
}
}

View file

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace Insight.Web.Models;
public record LocalStorage
{
[JsonPropertyName("darkmode")]
public bool DarkMode { get; init; }
}

View file

@ -0,0 +1,13 @@
using MongoDB.Bson;
namespace Insight.Web.Models;
public class SessionState
{
public string? Id { get; set; }
public ObjectId? Uid { get; set; }
public bool Connected { get; set; }
public bool Authenticated { get; set; }
public string? Username { get; set; }
public string? Page { get; set; }
}

View file

@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace Insight.Web.Models;
public record SessionStorage
{
[JsonPropertyName("drawer")]
public bool Drawer { get; init; } = true;
[JsonPropertyName("mainmenu")]
public MainMenu MainMenu { get; init; } = new();
}
public record MainMenu
{
[JsonPropertyName("management")]
public bool Management { get; init; }
}

View file

@ -0,0 +1,24 @@
using Insight.Web.Interfaces;
using Insight.Web.Messages;
using Vaitr.Bus;
namespace Insight.Web.Network.Handlers
{
public class ConsoleHandler : IWebMessageHandler<WebSession>
{
private readonly Bus _bus;
public ConsoleHandler(Bus bus)
{
_bus = bus;
}
public async ValueTask HandleAsync<TMessage>(WebSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IWebMessage
{
if (message is ConsoleQueryProxy consoleQuery)
{
await _bus.PublishAsync(consoleQuery, cancellationToken);
}
}
}
}

View file

@ -0,0 +1,56 @@
using Insight.Web.Interfaces;
using Insight.Web.Messages;
using Vaitr.Network;
namespace Insight.Web.Network
{
public class WebSession : TcpSession<IWebMessage>
{
private readonly IEnumerable<IWebMessageHandler<WebSession>> _handlers;
public WebSession(IEnumerable<IWebMessageHandler<WebSession>> handlers, ISerializer<IWebMessage> serializer, ILogger<WebSession> logger) : base(serializer, logger)
{
_handlers = handlers;
}
protected override ValueTask OnConnectedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Web ({ep?}) connected", RemoteEndPoint);
return default;
}
protected override ValueTask OnDisconnectedAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Web ({ep?}) disconnected", RemoteEndPoint);
return default;
}
protected override ValueTask OnSentAsync(IPacketContext<IWebMessage> context, CancellationToken cancellationToken)
{
return base.OnSentAsync(context, cancellationToken);
}
protected override async ValueTask OnReceivedAsync(IPacketContext<IWebMessage> context, CancellationToken cancellationToken)
{
await base.OnReceivedAsync(context, cancellationToken);
foreach (var handler in _handlers)
{
try
{
await handler.HandleAsync(this, context.Packet, cancellationToken);
}
catch (Exception ex)
{
_logger.LogWarning("Web ({ep?}) {ex}", RemoteEndPoint, ex.ToString());
}
}
}
protected override ValueTask OnHeartbeatAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Web ({ep?}) Heartbeat", RemoteEndPoint);
return default;
}
}
}

View file

@ -0,0 +1,130 @@
@using Insight.Web.Models.Account;
@inherits ComponentBase
@layout LoginLayout
@inject IJSRuntime JSRuntime
<PageTitle>@_title</PageTitle>
<style>
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0px 1000px white inset !important;
}
.mud-input{
background-color: white;
}
.blur-layer {
position: absolute;
backdrop-filter: blur(10px);
opacity: 0.5;
}
.app-caption {
font-weight: bold;
pointer-events: none;
user-select: none;
-moz-user-select: none; /* Firefox */
-webkit-user-select: none; /* Chrome and Safari */
-ms-user-select: none; /* IE 10+ and Edge */
}
.app-caption {
font-weight: bold;
pointer-events: none;
user-select: none;
-moz-user-select: none; /* Firefox */
-webkit-user-select: none; /* Chrome and Safari */
-ms-user-select: none; /* IE 10+ and Edge */
}
</style>
<div id="particles" style="width: 100%; height: calc(100vh - 6px); position: absolute;" />
<MudGrid Justify="Justify.Center" Spacing="0" Style="height: 100vh; align-items: center;">
<MudItem xs="12" md="6" lg="5" xl="4" xxl="3" Style="height: 100%;">
<div style="position: relative; height: 100%;">
<div class="blur-layer" style="height: 100%; width: 100%; z-index: 2;"></div>
<div style="display: flex; align-items: center; justify-content: center; height: 100%;">
<div style="width: 80%; z-index: 3;">
<div class="d-md-none d-block">
<p class="app-caption" style="font-size: 30px; color: black;">
INSIGHT
</p>
</div>
@if (Key is null)
{
<EditForm Model="@_model" OnValidSubmit="()=>SubmitAsync(_model)">
<MudGrid>
<MudItem xs="12" md="12" lg="12">
<MudTextField T="string" @bind-Value="_model.Email" For="()=>_model.Email" Placeholder="E-Mail" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="12" lg="12">
<MudTextField @bind-Value="_model.Password" For="()=>_model.Password" InputType="@(_passwordVisible ? InputType.Text : InputType.Password)"
Placeholder="Password" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@(_passwordVisible ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility)"
OnAdornmentClick="()=>_passwordVisible = !_passwordVisible" />
</MudItem>
<MudItem xs="12" md="12" lg="12" Class="d-flex justify-space-between">
<MudSpacer />
<MudCheckBox T="bool" Label="Remember" @bind-Value="_model.RememberMe" Color="Color.Primary" Class="ml-n1" />
</MudItem>
<MudItem xs="12" md="12" lg="12" Class="d-flex justify-center">
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Outlined" DisableElevation Color="Color.Surface" Size="Size.Large" Style="width: 100%; background-color: white;">
Login
</MudButton>
</MudItem>
</MudGrid>
</EditForm>
}
else
{
<MudGrid>
<MudItem xs="12" md="12" lg="12">
<MudTextField T="string" @bind-Value="_code" Placeholder="Authenticator Token" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="12" lg="12" Class="d-flex justify-center">
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Outlined" DisableElevation Color="Color.Surface" Size="Size.Large" Style="width: 100%; background-color: white;" OnClick="()=>SubmitTwoFactor(_code)">
Authenticate
</MudButton>
</MudItem>
</MudGrid>
}
</div>
</div>
</div>
</MudItem>
<MudItem xs="12" md="6" lg="7" xl="8" xxl="9" Class="d-none d-md-block" Style="background-color: black; width:100%; height:100%;">
<div style="display: flex; align-items: center; justify-content: center; height: 100%;">
<div style="z-index: 3;">
<p class="app-caption font-size-responsive" style="color: white;">
INSIGHT
</p>
</div>
</div>
</MudItem>
</MudGrid>
@code{
private LoginModel _model = new();
private string? _code;
private bool _passwordVisible;
private async Task InitializeParticlesAsync()
{
await JSRuntime.InvokeVoidAsync("LoadParticles", "particles");
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await InitializeParticlesAsync();
}
}
}

View file

@ -0,0 +1,76 @@
using Insight.Infrastructure.Entities;
using Insight.Infrastructure.Services;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Insight.Web.Middleware;
using Insight.Web.Models.Account;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Identity;
using MudBlazor;
namespace Insight.Web.Pages.Account;
[Route(Navigation.Account.Login)]
[Route(Navigation.Account.LoginTFA)]
public partial class Login
{
[Parameter] public Guid? Key { get; set; }
[Inject] private UserManager<InsightUser> UserManager { get; init; } = default!;
[Inject] private IdentityService IdentityService { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private readonly string _title = "LoginInsight";
private async Task SubmitAsync(LoginModel model)
{
if (string.IsNullOrWhiteSpace(model.Email))
{
Notification.Error(Snackbar, "Invalid E-Mail");
return;
}
if (string.IsNullOrWhiteSpace(model.Password))
{
Notification.Error(Snackbar, "Invalid Password");
return;
}
var user = await IdentityService.GetByEmailAsync(model.Email).ConfigureAwait(false);
if (user is null || user.LockoutEnabled && user.LockoutEnd > DateTime.UtcNow || await UserManager.CheckPasswordAsync(user, model.Password) is false)
{
Notification.Error(Snackbar, "Access denied");
return;
}
var query = NavigationManager.GetQueryString();
if (query.TryGetValue("redirect", out var redirect))
{
model.Redirect = redirect;
}
IdentityMiddleware.Logins[model.Key] = model;
NavigationManager.NavigateTo(Navigation.Account.SignInHref(model.Key), true);
}
private void SubmitTwoFactor(string? code)
{
if (string.IsNullOrWhiteSpace(code))
{
Notification.Information(Snackbar, "Enter Authenticator Code");
return;
}
try
{
IdentityMiddleware.Logins[Key.Value].TwoFactorToken = code;
NavigationManager.NavigateTo(Navigation.Account.SignInTFAHref(Key.Value), true);
}
catch (Exception ex)
{
Notification.Error(Snackbar, "Invalid Security Token");
NavigationManager.NavigateTo(Navigation.Account.LoginHref(null), false);
}
}
}

View file

@ -0,0 +1,32 @@
@inherits ComponentBase
@if (true)
{
return;
}
<BaseContainer Title="@_title">
<Content>
<div class="h-100" style="display:flex; height: 90vh; margin: auto;">
<div style="margin:auto; height: 35vh;">
<MudGrid>
<MudItem xs="12" md="12" lg="12">
<MudTextField T="string" Label="Authenticator Token" Placeholder="Token" @bind-Value="_code"
AutoFocus Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="12" lg="12" Class="d-flex justify-center">
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Surface" Size="Size.Large" Style="width: 100%;" OnClick="()=>Submit(_code)">
Login
</MudButton>
</MudItem>
</MudGrid>
</div>
</div>
</Content>
</BaseContainer>
@code{
private string? _code;
}

View file

@ -0,0 +1,29 @@
using Insight.Web.Constants;
using Insight.Web.Middleware;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Insight.Web.Pages.Account;
//[Route(Navigation.Account.LoginTFA)]
public partial class LoginTFA
{
[Parameter] public Guid Key { get; set; }
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private readonly string _title = "LoginInsight";
private void Submit(string? code)
{
if (string.IsNullOrWhiteSpace(code))
{
Notification.Information(Snackbar, "Enter Authenticator Code");
return;
}
IdentityMiddleware.Logins[Key].TwoFactorToken = code;
NavigationManager.NavigateTo(Navigation.Account.SignInTFAHref(Key), true);
}
}

View file

@ -0,0 +1,30 @@
@inherits ComponentBase
<BaseContainer Title="@_title" Breadcrumbs="@_breadcrumbs">
<Content>
@if (Account is not null)
{
<MudGrid>
<MudItem xs="12" sm="6" md="6" lg="3">
<KeyValueCard T="string" Key="E-Mail" Value="@Account.Email" Icon="@Icons.Material.Outlined.Email" />
</MudItem>
<MudItem xs="12" sm="6" md="6" lg="3">
<KeyValueCard T="string" Key="Password" Value="@("******")" OnClick="()=>_passwordDialog?.Toggle()" Icon="@Icons.Material.Outlined.Password" />
</MudItem>
<MudItem xs="12" sm="6" md="6" lg="3">
<KeyValueCard T="string" Key="2FA" Value="@(Account.TwoFactorEnabled ? "Enabled" : "Disabled")" Icon="@Icons.Material.Outlined.SecurityUpdateGood" OnClick="()=>_twoFactorDialog?.Toggle()" />
</MudItem>
</MudGrid>
<CascadingValue Name="User" Value="@Account">
<ProfilePasswordDialog @ref="_passwordDialog" />
<ProfileTwoFactorDialog @ref="_twoFactorDialog" OnToggle="OnRefreshAsync" />
</CascadingValue>
}
</Content>
</BaseContainer>
@code{
private ProfilePasswordDialog? _passwordDialog;
private ProfileTwoFactorDialog? _twoFactorDialog;
}

View file

@ -0,0 +1,40 @@
using Insight.Infrastructure.Entities;
using Insight.Infrastructure.Services;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using MudBlazor;
namespace Insight.Web.Pages.Account;
[Route(Navigation.Account.Profile)]
public partial class Profile
{
[Parameter] public InsightUser? Account { get; set; }
[Inject] private IdentityService IdentityService { get; init; } = default!;
[Inject] private AuthenticationStateProvider AuthenticationStateProvider { get; init; } = default!;
private readonly string _title = "ProfileInsight";
private readonly List<BreadcrumbItem> _breadcrumbs = new()
{
new BreadcrumbItem("Home", href: Navigation.Home),
new BreadcrumbItem("Account", href: Navigation.Account.Profile),
new BreadcrumbItem("Profile", href: "#", true)
};
protected override async Task OnInitializedAsync()
{
await OnRefreshAsync();
}
private async Task OnRefreshAsync()
{
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (state?.User?.Identity?.Name is null) return;
Account = await IdentityService.GetByEmailAsync(state.User.Identity.Name).ConfigureAwait(false);
await InvokeAsync(StateHasChanged);
}
}

View file

@ -0,0 +1,55 @@
@using Insight.Web.Models.Account;
<MudDrawer @bind-Open="_visible" Anchor="Anchor.End" Elevation="0" Variant="@DrawerVariant.Temporary" ClipMode="DrawerClipMode.Always" Width="400px" Style="max-width:auto;">
<MudDrawerHeader>
<MudText Typo="Typo.h6">
Password
</MudText>
</MudDrawerHeader>
<MudStack Justify="Justify.Center" Class="px-6">
<EditForm Model="@_model" OnValidSubmit="()=>Submit(_model)">
<DataAnnotationsValidator />
<MudStack Justify="Justify.Center" Spacing="5">
<MudItem>
<MudTextField Label="Current" Variant="Variant.Text" @bind-Value="_model.OldPassword" For="()=>_model.OldPassword"
Margin="Margin.Dense" InputType="@(_passwordCurrentVisible ? InputType.Text : InputType.Password)"
Adornment="Adornment.End" AdornmentIcon="@(_passwordCurrentVisible ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility)"
OnAdornmentClick="()=>_passwordCurrentVisible = !_passwordCurrentVisible" />
</MudItem>
<MudItem>
<MudTextField Label="New" Variant="Variant.Text" @bind-Value="_model.NewPassword" For="()=>_model.NewPassword"
Margin="Margin.Dense" InputType="@(_passwordNewVisible ? InputType.Text : InputType.Password)"
Adornment="Adornment.End" AdornmentIcon="@(_passwordNewVisible ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility)"
OnAdornmentClick="()=>_passwordNewVisible = !_passwordNewVisible" />
</MudItem>
<MudItem>
<MudTextField Label="Confirm New" Variant="Variant.Text" @bind-Value="_model.ConfirmNewPassword" For="()=>_model.ConfirmNewPassword"
Margin="Margin.Dense" InputType="@(_passwordCofirmVisible ? InputType.Text : InputType.Password)"
Adornment="Adornment.End" AdornmentIcon="@(_passwordCofirmVisible ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility)"
OnAdornmentClick="()=>_passwordCofirmVisible = !_passwordCofirmVisible" />
</MudItem>
<MudItem Class="d-flex justify-center mt-7">
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Secondary" Size="Size.Large" DisableElevation Style="width: 100%;">
Change Password
</MudButton>
</MudItem>
</MudStack>
</EditForm>
</MudStack>
</MudDrawer>
@code{
private bool _visible;
private bool _passwordCurrentVisible;
private bool _passwordNewVisible;
private bool _passwordCofirmVisible;
private ChangePasswordModel _model = new();
public void Toggle()
{
_visible = !_visible;
StateHasChanged();
}
}

View file

@ -0,0 +1,30 @@
using Insight.Infrastructure.Entities;
using Insight.Web.Constants;
using Insight.Web.Middleware;
using Insight.Web.Models.Account;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Insight.Web.Pages.Account;
public partial class ProfilePasswordDialog
{
[CascadingParameter(Name = "User")] public InsightUser? User { get; set; }
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private void Submit(ChangePasswordModel model)
{
if (model.NewPassword != model.ConfirmNewPassword)
{
Notification.Error(Snackbar, "Passwords not matching");
return;
}
var key = Guid.NewGuid();
IdentityMiddleware.Passwords[key] = model;
NavigationManager.NavigateTo(Navigation.Account.ChangePasswordHref(key), true);
}
}

View file

@ -0,0 +1,221 @@
@using System.ComponentModel.DataAnnotations;
<MudDrawer @bind-Open="Visible" Anchor="Anchor.End" Elevation="0" Variant="@DrawerVariant.Temporary" ClipMode="DrawerClipMode.Always" Width="400px">
<MudDrawerHeader>
<MudText Typo="Typo.h6">
2FA
</MudText>
</MudDrawerHeader>
<MudStack Justify="Justify.Center" Class="px-6" Spacing="3">
<div class="d-flex justify-center" style="height: @(!_showQrCode ? 0 : 272)px;">
<span id="qrc" hidden="@(!_showQrCode)" style="border-style: solid; border-color: white; border-width: 1px;" />
@*<div style="width:50px;" />
<MudText Align="Align.Left" Typo="Typo.h6" hidden="@(!_showQrCode)" Style="text-decoration:overline;">
@AuthenticatorFormatKey
</MudText>*@
</div>
<div hidden="@(!_showQrCode)">
@*<MudDivider />*@
<MudText Align="Align.Center" Typo="Typo.subtitle2" Style="text-decoration:none;">
@_authenticatorFormatKey
</MudText>
@*<MudDivider />*@
</div>
@if (_content == Content.Options)
{
@if (true)
{
_showQrCode = false;
}
@*@if (false) // test
{
<MudButton OnClick="()=>OnChangeAsync(Content.UseRecovery)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Info">
Recovery
</MudButton>
}*@
@if (_enabled)
{
<MudButton OnClick="()=>OnChangeAsync(Content.Validate)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Info">
Add Device
</MudButton>
}
@if (_enabled)
{
<MudButton OnClick="DisableAsync" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Warning">
Disable
</MudButton>
}
else
{
<MudButton OnClick="()=>OnChangeAsync(Content.Enable)" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Success">
Enable
</MudButton>
}
<MudDivider Class="mt-3 mb-3" />
<MudButton OnClick="()=>OnChangeAsync(Content.ResetRecovery)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Error">
Reset Recovery Codes
</MudButton>
<MudButton OnClick="()=>OnChangeAsync(Content.Delete)" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Error">
Delete Authenticator
</MudButton>
}
else if (_content == Content.Validate)
{
@if (true)
{
_showQrCode = true;
}
<EditForm Model="@_code" OnValidSubmit="()=>ValidateAsync(_code)" class="mt-3">
<MudTextField T="string" Label="Authenticator Code" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_code" Clearable AutoFocus />
<MudStack Justify="Justify.Center" Row Class="mt-4">
<MudButton OnClick="()=>OnChangeAsync(Content.Options)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Surface">
Cancel
</MudButton>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Info" Style="width:100%;">
Validate
</MudButton>
</MudStack>
</EditForm>
}
else if (_content == Content.Enable)
{
@if (true)
{
_showQrCode = true;
}
<MudTextField T="string" @bind-Value="@_code" Label="Authenticator Code" Variant="Variant.Outlined" Margin="Margin.Dense" Clearable AutoFocus />
<MudStack Justify="Justify.Center" Row Class="mt-4">
<MudButton OnClick="()=>OnChangeAsync(Content.Options)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Surface">
Cancel
</MudButton>
<MudButton OnClick="()=>EnableAsync(_code)" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Success" Style="width: 100%;">
Enable
</MudButton>
</MudStack>
}
else if (_content == Content.Delete)
{
@if (true)
{
_showQrCode = false;
}
<MudStack Justify="Justify.Center" Row>
<MudButton OnClick="()=>OnChangeAsync(Content.Options)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Surface">
Cancel
</MudButton>
<MudButton OnClick="DeleteAsync" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Error" FullWidth>
Confirm Delete
</MudButton>
</MudStack>
}
else if (_content == Content.UseRecovery)
{
@if (true)
{
_showQrCode = false;
}
<EditForm Model="@_code" OnValidSubmit="()=>UseRecoveryAsync(_code)">
<MudTextField T="string" Label="Recovery Code" Variant="Variant.Outlined" Margin="Margin.Dense" @bind-Value="_code" Clearable AutoFocus />
<MudStack Justify="Justify.Center" Row Class="mt-4">
<MudButton OnClick="()=>OnChangeAsync(Content.Options)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Surface">
Cancel
</MudButton>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Error" Style="width: 100%;">
Recover
</MudButton>
</MudStack>
</EditForm>
}
else if (_content == Content.ResetRecovery)
{
@if (true)
{
_showQrCode = false;
}
<MudStack Justify="Justify.Center" Row>
<MudButton OnClick="()=>OnChangeAsync(Content.Options)" Variant="Variant.Outlined" DisableElevation Size="Size.Large" Color="Color.Surface">
Cancel
</MudButton>
<MudButton OnClick="ResetRecoveryAsync" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Error" FullWidth>
Confirm Reset
</MudButton>
</MudStack>
}
else if (_content == Content.ShowRecovery)
{
@if (true)
{
_showQrCode = false;
}
@if (_recoveryCodes != null)
{
@foreach (var rc in _recoveryCodes)
{
<MudText Align="Align.Center" Typo="Typo.h4">
@rc
</MudText>
<MudDivider Class="mt-3 mb-3" />
}
}
<MudButton OnClick="()=>OnChangeAsync(Content.Options)" Variant="Variant.Filled" DisableElevation Size="Size.Large" Color="Color.Warning">
Acknowledged
</MudButton>
}
</MudStack>
</MudDrawer>
@code
{
private string? _code;
private bool _visible;
private bool _showQrCode;
private bool _enabled;
private int _recoveryCount;
private List<string>? _recoveryCodes;
private string? _authenticatorKey;
private string? _authenticatorFormatKey;
public bool Visible
{
get
{
return _visible;
}
set
{
if (_visible == value) return;
if (value) _content = Content.Options;
_visible = value;
StateHasChanged();
if (OnToggle is not null)
{
OnToggle();
}
}
}
public void Toggle()
{
Visible = !Visible;
}
}

View file

@ -0,0 +1,217 @@
using Insight.Infrastructure.Entities;
using Insight.Infrastructure.Services;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using MudBlazor;
namespace Insight.Web.Pages.Account;
public partial class ProfileTwoFactorDialog
{
[CascadingParameter(Name = "User")] public InsightUser? User { get; set; }
[Parameter] public Func<Task>? OnToggle { get; set; }
[Inject] private AuthenticatorService AuthenticatorService { get; init; } = default!;
[Inject] private IJSRuntime JSRuntime { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private enum Content { Options, Validate, Enable, Delete, UseRecovery, ResetRecovery, ShowRecovery }
private Content _content = Content.Options;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await RefreshAsync().ConfigureAwait(false);
}
}
private async Task RefreshAsync()
{
if (User is null) return;
_authenticatorKey = await AuthenticatorService.GetKeyAsync(User).ConfigureAwait(false);
if (string.IsNullOrEmpty(_authenticatorKey)) // if no key exists, generate one + recovery keys
{
await AuthenticatorService.ResetKeyAsync(User).ConfigureAwait(false);
_authenticatorKey = await AuthenticatorService.GetKeyAsync(User).ConfigureAwait(false);
var recoveryCodes = await AuthenticatorService.ResetRecoveryCodesAsync(User).ConfigureAwait(false);
if (recoveryCodes is not null)
{
_recoveryCodes = new List<string>(recoveryCodes);
}
}
if (_authenticatorKey is not null) _authenticatorFormatKey = AuthenticatorService.HumanizeKey(_authenticatorKey);
_enabled = await AuthenticatorService.GetStatusAsync(User).ConfigureAwait(false);
_recoveryCount = await AuthenticatorService.CountRecoveryCodesAsync(User).ConfigureAwait(false);
await ClearQrCodeAsync("qrc").ConfigureAwait(false);
await CreateQrCodeAsync("qrc", 270, 270).ConfigureAwait(false);
}
private async Task OnChangeAsync(Content content)
{
await RefreshAsync().ConfigureAwait(false);
// if active site displays recovery codes, clear on switch
if (_content == Content.ShowRecovery) _recoveryCodes = null;
_content = content;
await InvokeAsync(StateHasChanged).ConfigureAwait(false);
}
private async Task ValidateAsync(string? code)
{
if (User is null || string.IsNullOrWhiteSpace(code)) return;
var validation = await AuthenticatorService.VerifyAsync(User, code).ConfigureAwait(false);
if (validation is false)
{
Notification.Error(Snackbar, "Invalid 2FA Code");
return;
}
Notification.Success(Snackbar, "Valid 2FA Code");
await OnChangeAsync(Content.Options).ConfigureAwait(false);
}
private async Task EnableAsync(string? code)
{
Console.WriteLine(_code);
if (User is null)
{
Notification.Error(Snackbar, "user null");
return;
}
if (string.IsNullOrWhiteSpace(code))
{
Notification.Error(Snackbar, $"code null ({code})");
return;
}
var validation = await AuthenticatorService.VerifyAsync(User, code).ConfigureAwait(false);
if (validation is false)
{
Notification.Error(Snackbar, "Invalid 2FA Code");
return;
}
var result = await AuthenticatorService.EnableAsync(User).ConfigureAwait(false);
if (result is false)
{
Notification.Error(Snackbar, "Error");
return;
}
Notification.Success(Snackbar, "Enabled 2FA");
await OnChangeAsync(Content.Options).ConfigureAwait(false);
}
private async Task DisableAsync()
{
if (User is null) return;
var result = await AuthenticatorService.DisableAsync(User).ConfigureAwait(false);
if (result is false)
{
Notification.Error(Snackbar, "Error");
return;
}
Notification.Success(Snackbar, "Disabled 2FA");
await OnChangeAsync(Content.Options).ConfigureAwait(false);
}
private async Task DeleteAsync()
{
if (User is null) return;
var result = await AuthenticatorService.DeleteAsync(User).ConfigureAwait(false);
if (result is false)
{
Notification.Error(Snackbar, "Error");
return;
}
Notification.Success(Snackbar, "Deleted 2FA");
await OnChangeAsync(Content.Options).ConfigureAwait(false);
}
private async Task UseRecoveryAsync(string? code)
{
if (User is null || string.IsNullOrWhiteSpace(code)) return;
await CountRecoveryAsync().ConfigureAwait(false);
if (_recoveryCount == 0)
{
Notification.Error(Snackbar, "No Recovery Codes Left");
return;
}
var result = await AuthenticatorService.UseRecoveryCodeAsync(User, code).ConfigureAwait(false);
if (result is false)
{
Notification.Error(Snackbar, "Invalid Code");
return;
}
await CountRecoveryAsync().ConfigureAwait(false);
Notification.Success(Snackbar, $"Recovery Codes Left: ({_recoveryCount})");
await OnChangeAsync(Content.Options).ConfigureAwait(false);
}
private async Task ResetRecoveryAsync()
{
if (User is null) return;
var recoveryCodes = await AuthenticatorService.ResetRecoveryCodesAsync(User).ConfigureAwait(false);
if (recoveryCodes is null)
{
Notification.Error(Snackbar, "Error");
return;
}
if (recoveryCodes is not null)
{
_recoveryCodes = new List<string>(recoveryCodes);
}
Notification.Success(Snackbar, "Reset 2FA Recovery Codes");
await OnChangeAsync(Content.ShowRecovery).ConfigureAwait(false);
}
private async Task CountRecoveryAsync()
{
if (User is null) return;
_recoveryCount = await AuthenticatorService.CountRecoveryCodesAsync(User).ConfigureAwait(false);
}
private async Task CreateQrCodeAsync(string elementId, int width, int height)
{
if (User is null || User.Email is null) return;
if (_authenticatorKey is null) return;
var code = AuthenticatorService.GenerateQrCode(User.Email, _authenticatorKey);
await JSRuntime.InvokeVoidAsync("createQrCode", elementId, code, width, height).ConfigureAwait(false);
}
private async Task ClearQrCodeAsync(string elementId)
{
await JSRuntime.InvokeVoidAsync("clearQrCode", elementId).ConfigureAwait(false);
}
}

View file

@ -0,0 +1,9 @@
@page "/"
@inject NavigationManager NavManager
@code {
protected override void OnInitialized()
{
NavManager.NavigateTo(Navigation.Monitoring.Maintenance.Index);
}
}

View file

@ -0,0 +1 @@
<h1>@IsSuccess</h1>

View file

@ -0,0 +1,28 @@
using Insight.Infrastructure.Services;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
namespace Insight.Web.Pages.Internal;
[Route(Navigation.Internal.Seed)]
public partial class Seed
{
[Inject] private IdentityService IdentityService { get; init; } = default!;
[Inject] private ILogger<Seed> Logger { get; init; } = default!;
private bool IsSuccess = false;
protected override async Task OnInitializedAsync()
{
try
{
await IdentityService.SeedAsync();
IsSuccess = true;
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
IsSuccess = false;
}
}
}

View file

@ -0,0 +1,50 @@
<div class="mt-7">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
<MudGrid>
<MudItem xs="12" sm="6" md="6">
<p><b>Current Session</b></p>
<p> @SessionHandler.State.Id</p>
<br />
<p><b>Current Authenticated</b></p>
<p> @SessionHandler.State.Authenticated</p>
<br />
<p><b>Current User</b></p>
<p> @SessionHandler.State.Username</p>
<br />
<p><b>Active User Sessions</b></p>
@{
foreach (var cs in SessionPool.Sessions.Where(p => p.Value.Connected && p.Value.Username == SessionHandler.State.Username))
{
<p>@($"{cs.Key} ({cs.Value?.Username})")</p>
}
}
</MudItem>
<MudItem xs="12" sm="6" md="6">
<p><b>All Sessions</b></p>
@{
foreach (var cs in SessionPool.Sessions)
{
<p>@($"{cs.Key} ({cs.Value?.Username})")</p>
}
}
<br />
<p><b>Active Sessions</b></p>
@{
foreach (var cs in SessionPool.Sessions.Where(p => p.Value.Connected))
{
<p>@($"{cs.Key} ({cs.Value?.Username})")</p>
}
}
<br />
<p><b>Starving Sessions</b></p>
@{
foreach (var cs in SessionPool.Sessions.Where(p => p.Value.Connected is false))
{
<p>@($"{cs.Key} ({cs.Value?.Username})")</p>
}
}
</MudItem>
</MudGrid>
@HttpContextAccessor.HttpContext?.Connection?.RemoteIpAddress;
</MudContainer>
</div>

View file

@ -0,0 +1,14 @@
using Insight.Web.Constants;
using Insight.Web.Services;
using Microsoft.AspNetCore.Components;
namespace Insight.Web.Pages.Internal;
[Route(Navigation.Internal.Sessions)]
public partial class Sessions
{
[Inject] private SessionPool SessionPool { get; init; } = default!;
[Inject] private SessionHandler SessionHandler { get; init; } = default!;
[Inject] private IHttpContextAccessor HttpContextAccessor { get; init; } = default!;
[Inject] private ILogger<Sessions> Logger { get; init; } = default!;
}

View file

@ -0,0 +1,66 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Firmware" T="ViewModel">
Firmware
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Serial" T="ViewModel">
Serial
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Status" T="ViewModel">
Status
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Firmware">
@context?.Firmware
</MudTd>
<MudTd DataLabel="Serial">
@context?.Serial
</MudTd>
<MudTd DataLabel="Status">
@{
var color = context?.Status?.ToLower() switch
{
"ok" => Color.Success,
_ => Color.Error
};
<MudText Color="color" Typo="Typo.inherit">
@context?.Status
</MudText>
}
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,142 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Drives;
[Route(Navigation.Inventory.Hardware.Drives.Hosts)]
public partial class Hosts
{
[Parameter] public string? DriveName { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Drives", href: Navigation.Inventory.Hardware.Drives.Index));
if (string.IsNullOrWhiteSpace(DriveName))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Hardware.Drives.Index);
}
Title = $"Inventory » Drives » {DriveName} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(DriveName, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("firmware", regex) |
Builders<BsonDocument>.Filter.Regex("serial", regex) |
Builders<BsonDocument>.Filter.Regex("status", regex);
}
var query = Database.HostDrive()
.Aggregate()
.Match(Builders<HostDriveEntity>.Filter.Regex(p => p.Name, new BsonRegularExpression(new Regex(DriveName.Escape(), RegexOptions.IgnoreCase))))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "firmware", 1 },
{ "serial", 1 },
{ "status", 1 },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Firmware" => Builders<BsonDocument>.Sort.Ascending("firmware"),
"Serial" => Builders<BsonDocument>.Sort.Ascending("serial"),
"Status" => Builders<BsonDocument>.Sort.Ascending("status"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Firmware" => Builders<BsonDocument>.Sort.Descending("firmware"),
"Serial" => Builders<BsonDocument>.Sort.Descending("serial"),
"Status" => Builders<BsonDocument>.Sort.Descending("status"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("firmware")]
public string? Firmware { get; set; }
[BsonElement("serial")]
public string? Serial { get; set; }
[BsonElement("status")]
public string? Status { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

View file

@ -0,0 +1,58 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Name" T="ViewModel">
Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Type" T="ViewModel">
Type
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Size" T="ViewModel">
Capacity
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Name">
@context?.Name
</MudTd>
<MudTd DataLabel="Type">
@context?.Type
</MudTd>
<MudTd DataLabel="Size">
@{
double? size = null;
var value = "-";
if (context?.Capacity is not null)
{
size = Math.Round((context.Capacity.Value / Math.Pow(1024, 3)), 2);
value = $"{size} GB";
}
@value
}
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Hardware.Drives.HostsHref(context?.Name)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,126 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Drives;
[Route(Navigation.Inventory.Hardware.Drives.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » DrivesInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Drives", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostDriveEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostDriveEntity>.Filter.Regex(x => x.Company, regex) |
Builders<HostDriveEntity>.Filter.Regex(x => x.Name, regex);
}
var query = Database.HostDrive()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("name", "$name") },
{ "name", new BsonDocument("$first", "$name") },
{ "type", new BsonDocument("$first", "$type") },
{ "size", new BsonDocument("$first", "$size") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "name", 1 },
{ "type", 1 },
{ "size", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Name" => Builders<BsonDocument>.Sort.Ascending("name"),
"Type" => Builders<BsonDocument>.Sort.Ascending("type"),
"Size" => Builders<BsonDocument>.Sort.Ascending("size"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Name" => Builders<BsonDocument>.Sort.Descending("name"),
"Type" => Builders<BsonDocument>.Sort.Descending("type"),
"Size" => Builders<BsonDocument>.Sort.Descending("size"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("type")]
public string? Type { get; set; }
[BsonElement("size")]
public long? Capacity { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}

View file

@ -0,0 +1,49 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Version" T="ViewModel">
Version
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Serial" T="ViewModel">
Serial
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Version">
@context?.Version
</MudTd>
<MudTd DataLabel="Version">
@context?.Serial
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,135 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Mainboards;
[Route(Navigation.Inventory.Hardware.Mainboards.Hosts)]
public partial class Hosts
{
[Parameter] public string? MainboardName { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Mainboards", href: Navigation.Inventory.Hardware.Mainboards.Index));
if (string.IsNullOrWhiteSpace(MainboardName))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Hardware.Mainboards.Index);
return;
}
Title = $"Inventory » Mainboards » {MainboardName} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(MainboardName, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("version", regex);
}
var query = Database.HostMainboard()
.Aggregate()
.Match(Builders<HostMainboardEntity>.Filter.Regex(p => p.Name, new BsonRegularExpression(new Regex(MainboardName.Escape(), RegexOptions.IgnoreCase))))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "version", 1 },
{ "serial", 1 },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Version" => Builders<BsonDocument>.Sort.Ascending("version"),
"Serial" => Builders<BsonDocument>.Sort.Ascending("serial"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Version" => Builders<BsonDocument>.Sort.Descending("version"),
"Serial" => Builders<BsonDocument>.Sort.Descending("serial"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("version")]
public string? Version { get; set; }
[BsonElement("serial")]
public string? Serial { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

View file

@ -0,0 +1,39 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Name" T="ViewModel">
Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Bios" T="ViewModel">
Bios
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Name">
@context?.Name
</MudTd>
<MudTd DataLabel="Bios">
@context?.Bios
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Hardware.Mainboards.HostsHref(context?.Name)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,119 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Mainboards;
[Route(Navigation.Inventory.Hardware.Mainboards.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » MainboardsInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Mainboards", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostMainboardEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostMainboardEntity>.Filter.Regex(x => x.Bios, regex) |
Builders<HostMainboardEntity>.Filter.Regex(x => x.Name, regex);
}
var query = Database.HostMainboard()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("name", "$name") },
{ "bios", new BsonDocument("$first", "$bios") },
{ "name", new BsonDocument("$first", "$name") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "bios", 1 },
{ "name", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Bios" => Builders<BsonDocument>.Sort.Ascending("bios"),
"Name" => Builders<BsonDocument>.Sort.Ascending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Bios" => Builders<BsonDocument>.Sort.Descending("bios"),
"Name" => Builders<BsonDocument>.Sort.Descending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("bios")]
public string? Bios { get; set; }
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}

View file

@ -0,0 +1,49 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Location" T="ViewModel">
Location
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Serial" T="ViewModel">
Serial
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Location">
@context?.Location
</MudTd>
<MudTd DataLabel="Serial">
@context?.Serial
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,136 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Memory;
[Route(Navigation.Inventory.Hardware.Memory.Hosts)]
public partial class Hosts
{
[Parameter] public string? MemoryName { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Memory", href: Navigation.Inventory.Hardware.Memory.Index));
if (string.IsNullOrWhiteSpace(MemoryName))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Hardware.Memory.Index);
return;
}
Title = $"Inventory » Memory » {MemoryName} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(MemoryName, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("location", regex) |
Builders<BsonDocument>.Filter.Regex("serial", regex);
}
var query = Database.HostMemory()
.Aggregate()
.Match(Builders<HostMemoryEntity>.Filter.Regex(p => p.Name, new BsonRegularExpression(new Regex(MemoryName.Escape(), RegexOptions.IgnoreCase))))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "location", 1 },
{ "serial", 1 },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Location" => Builders<BsonDocument>.Sort.Ascending("location"),
"Serial" => Builders<BsonDocument>.Sort.Ascending("serial"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Location" => Builders<BsonDocument>.Sort.Descending("location"),
"Serial" => Builders<BsonDocument>.Sort.Descending("serial"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("location")]
public string? Location { get; set; }
[BsonElement("serial")]
public string? Serial { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

View file

@ -0,0 +1,39 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Company" T="ViewModel">
Company
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Name" T="ViewModel">
Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Company">
@context?.Company
</MudTd>
<MudTd DataLabel="Name">
@context?.Name
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Hardware.Memory.HostsHref(context?.Name)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,119 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Memory;
[Route(Navigation.Inventory.Hardware.Memory.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » MemoryInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Memory", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostMemoryEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostMemoryEntity>.Filter.Regex(x => x.Company, regex) |
Builders<HostMemoryEntity>.Filter.Regex(x => x.Name, regex);
}
var query = Database.HostMemory()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("name", "$name") },
{ "company", new BsonDocument("$first", "$company") },
{ "name", new BsonDocument("$first", "$name") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "company", 1 },
{ "name", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Company" => Builders<BsonDocument>.Sort.Ascending("company"),
"Name" => Builders<BsonDocument>.Sort.Ascending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Company" => Builders<BsonDocument>.Sort.Descending("company"),
"Name" => Builders<BsonDocument>.Sort.Descending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("company")]
public string? Company { get; set; }
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}

View file

@ -0,0 +1,49 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Version" T="ViewModel">
Version
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Serial" T="ViewModel">
Serial
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Version">
@context?.Version
</MudTd>
<MudTd DataLabel="Serial">
@context?.Serial
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,136 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Processors;
[Route(Navigation.Inventory.Hardware.Processors.Hosts)]
public partial class Hosts
{
[Parameter] public string? ProcessorName { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Processors", href: Navigation.Inventory.Hardware.Processors.Index));
if (string.IsNullOrWhiteSpace(ProcessorName))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Hardware.Processors.Index);
return;
}
Title = $"Inventory » Processors » {ProcessorName} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(ProcessorName, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("version", regex) |
Builders<BsonDocument>.Filter.Regex("serial", regex);
}
var query = Database.HostProcessor()
.Aggregate()
.Match(Builders<HostProcessorEntity>.Filter.Regex(p => p.Name, new BsonRegularExpression(new Regex(ProcessorName.Escape(), RegexOptions.IgnoreCase))))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "version", 1 },
{ "serial", 1 },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Version" => Builders<BsonDocument>.Sort.Ascending("version"),
"Serial" => Builders<BsonDocument>.Sort.Ascending("serial"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Version" => Builders<BsonDocument>.Sort.Descending("version"),
"Serial" => Builders<BsonDocument>.Sort.Descending("serial"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("version")]
public string? Version { get; set; }
[BsonElement("serial")]
public string? Serial { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

View file

@ -0,0 +1,39 @@
@inherits ComponentBase
<TableContainer T="IndexViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Company" T="IndexViewModel">
Company
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Name" T="IndexViewModel">
Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="IndexViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Company">
@context?.Company
</MudTd>
<MudTd DataLabel="Name">
@context?.Name
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Hardware.Processors.HostsHref(context?.Name)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,120 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Processors
{
[Route(Navigation.Inventory.Hardware.Processors.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<IndexViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » ProcessorsInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Processors", href: "#", true));
}
private async Task<TableData<IndexViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostProcessorEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostProcessorEntity>.Filter.Regex(x => x.Company, regex) |
Builders<HostProcessorEntity>.Filter.Regex(x => x.Name, regex);
}
var query = Database.HostProcessor()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("name", "$name") },
{ "company", new BsonDocument("$first", "$company") },
{ "name", new BsonDocument("$first", "$name") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "company", 1 },
{ "name", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Company" => Builders<BsonDocument>.Sort.Ascending("company"),
"Name" => Builders<BsonDocument>.Sort.Ascending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Company" => Builders<BsonDocument>.Sort.Descending("company"),
"Name" => Builders<BsonDocument>.Sort.Descending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<IndexViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<IndexViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<IndexViewModel>();
}
}
[BsonIgnoreExtraElements]
public class IndexViewModel
{
[BsonElement("company")]
public string? Company { get; set; }
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}
}

View file

@ -0,0 +1,49 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Driver" T="ViewModel">
Driver (Version)
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Date" T="ViewModel">
Driver (Date)
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Driver">
@context?.Driver
</MudTd>
<MudTd DataLabel="Date">
@context?.Date
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,137 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Videocards
{
[Route(Navigation.Inventory.Hardware.Videocards.Hosts)]
public partial class Hosts
{
[Parameter] public string? VideocardName { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Videocards", href: Navigation.Inventory.Hardware.Videocards.Index));
if (string.IsNullOrWhiteSpace(VideocardName))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Hardware.Videocards.Index);
return;
}
Title = $"Inventory » Videocards » {VideocardName} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(VideocardName, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("driver", regex) |
Builders<BsonDocument>.Filter.Regex("date", regex);
}
var query = Database.HostVideocard()
.Aggregate()
.Match(Builders<HostVideocardEntity>.Filter.Regex(p => p.Name, new BsonRegularExpression(new Regex(VideocardName.Escape(), RegexOptions.IgnoreCase))))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "driver", 1 },
{ "date", 1 },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Driver" => Builders<BsonDocument>.Sort.Ascending("driver"),
"Date" => Builders<BsonDocument>.Sort.Ascending("date"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Driver" => Builders<BsonDocument>.Sort.Descending("driver"),
"Date" => Builders<BsonDocument>.Sort.Descending("date"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("driver")]
public string? Driver { get; set; }
[BsonElement("date")]
public DateTime? Date { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}
}

View file

@ -0,0 +1,31 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Name" T="ViewModel">
Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Name">
@context?.Name
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Hardware.Videocards.HostsHref(context?.Name)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,113 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Hardware.Videocards
{
[Route(Navigation.Inventory.Hardware.Videocards.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » VideocardsInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Videocards", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostVideocardEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostVideocardEntity>.Filter.Regex(x => x.Company, regex) |
Builders<HostVideocardEntity>.Filter.Regex(x => x.Name, regex);
}
var query = Database.HostVideocard()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("name", "$name") },
{ "name", new BsonDocument("$first", "$name") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "name", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Name" => Builders<BsonDocument>.Sort.Ascending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Name" => Builders<BsonDocument>.Sort.Descending("name"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}
}

View file

@ -0,0 +1,51 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Interface" T="ViewModel">
Interface
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Mask" T="ViewModel">
Mask
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Interface">
<MudLink Href="@Navigation.Management.Hosts.Network.Interfaces.DetailsHref(@context?.Hosts?.Id, @context?.Interface?.Id.ToString())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Interface?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Mask">
@context?.Mask
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,139 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Addresses;
[Route(Navigation.Inventory.Network.Addresses.Hosts)]
public partial class Hosts
{
[Parameter] public string? Address { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Address = Address?.UriEscape(true);
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Addresses", href: Navigation.Inventory.Network.Addresses.Index));
if (string.IsNullOrWhiteSpace(Address))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Network.Addresses.Index);
return;
}
Title = $"Inventory » Address » {Address} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(Address, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("interface", regex) |
Builders<BsonDocument>.Filter.Regex("mask", regex);
}
var query = Database.HostInterfaceAddress()
.Aggregate()
.Match(Builders<HostInterfaceAddressEntity>.Filter.Eq(p => p.Address, Address))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Lookup("host_if", "_interface", "_id", "interfaces")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "mask", 1 },
{ "interface", new BsonDocument("$first", "$interfaces") },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Mask" => Builders<BsonDocument>.Sort.Ascending("mask"),
"Interface" => Builders<BsonDocument>.Sort.Ascending("interface.name"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Mask" => Builders<BsonDocument>.Sort.Descending("mask"),
"Interface" => Builders<BsonDocument>.Sort.Descending("interface.name"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("mask")]
public string? Mask { get; set; }
[BsonElement("interface")]
public HostInterfaceEntity? Interface { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

View file

@ -0,0 +1,32 @@
@inherits ComponentBase
@using Insight.Web.Extensions
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Address" T="ViewModel">
Address
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Address">
@context?.Address
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Network.Addresses.HostsHref(context?.Address?.UriEscape())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,111 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Addresses;
[Route(Navigation.Inventory.Network.Addresses.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » AddressesInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Addresses", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostInterfaceAddressEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostInterfaceAddressEntity>.Filter.Regex(x => x.Address, regex);
}
var query = Database.HostInterfaceAddress()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("address", "$address") },
{ "address", new BsonDocument("$first", "$address") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "address", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Address" => Builders<BsonDocument>.Sort.Ascending("address"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Address" => Builders<BsonDocument>.Sort.Descending("address"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("address")]
public string? Address { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}

View file

@ -0,0 +1,44 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Interface" T="ViewModel">
Interface
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Interface">
<MudLink Href="@Navigation.Management.Hosts.Network.Interfaces.DetailsHref(@context?.Hosts?.Id, @context?.Interface?.Id.ToString())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Interface?.Name
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,135 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Gateways
{
[Route(Navigation.Inventory.Network.Gateways.Hosts)]
public partial class Hosts
{
[Parameter] public string? GatewayAddress { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
GatewayAddress = GatewayAddress?.UriEscape(true);
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Gateways", href: Navigation.Inventory.Network.Gateways.Index));
if (string.IsNullOrWhiteSpace(GatewayAddress))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Network.Gateways.Index);
return;
}
Title = $"Inventory » Gateway » {GatewayAddress} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(GatewayAddress, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("interface", regex);
}
var query = Database.HostInterfaceGateway()
.Aggregate()
.Match(Builders<HostInterfaceGatewayEntity>.Filter.Eq(p => p.Address, GatewayAddress))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Lookup("host_if", "_interface", "_id", "interfaces")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "interface", new BsonDocument("$first", "$interfaces") },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Interface" => Builders<BsonDocument>.Sort.Ascending("interface.name"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Interface" => Builders<BsonDocument>.Sort.Descending("interface.name"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception ex)
{
Console.WriteLine(ex);
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("interface")]
public HostInterfaceEntity? Interface { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}
}

View file

@ -0,0 +1,32 @@
@inherits ComponentBase
@using Insight.Web.Extensions
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Address" T="ViewModel">
Address
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Address">
@context?.Address
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Network.Gateways.HostsHref(context?.Address?.UriEscape())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,111 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Gateways;
[Route(Navigation.Inventory.Network.Gateways.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » GatewaysInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Gateways", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostInterfaceGatewayEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostInterfaceGatewayEntity>.Filter.Regex(x => x.Address, regex);
}
var query = Database.HostInterfaceGateway()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("address", "$address") },
{ "address", new BsonDocument("$first", "$address") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "address", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Address" => Builders<BsonDocument>.Sort.Ascending("address"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Address" => Builders<BsonDocument>.Sort.Descending("address"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("address")]
public string? Address { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}

View file

@ -0,0 +1,92 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Name" T="ViewModel">
Name
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Description" T="ViewModel">
Adapter
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Mac" T="ViewModel">
Mac
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Suffix" T="ViewModel">
Suffix
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Status" T="ViewModel">
Status
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Name">
<MudLink Href="@Navigation.Management.Hosts.Network.Interfaces.DetailsHref(@context?.Hosts?.Id, context?.Id?.ToString())" Typo="Typo.inherit" Underline="Underline.None">
@{
var value = context?.Name;
if (string.IsNullOrWhiteSpace(value)) value = "N/A";
@value
}
</MudLink>
</MudTd>
<MudTd DataLabel="Description">
@context?.Description
</MudTd>
<MudTd DataLabel="Mac">
@context?.Mac
</MudTd>
<MudTd DataLabel="Suffix">
@context?.Suffix
</MudTd>
<MudTd DataLabel="Status">
@if (context?.Status is not null && Enum.TryParse<System.Net.NetworkInformation.OperationalStatus>(context.Status, true, out var operationalStatus))
{
var color = operationalStatus == System.Net.NetworkInformation.OperationalStatus.Up ? Color.Success : Color.Error;
<MudText Color="color" Typo="Typo.inherit">
@context?.Status
</MudText>
}
else
{
@("Unknown")
}
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,145 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Interfaces;
[Route(Navigation.Inventory.Network.Interfaces.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » InterfacesInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Interfaces", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var search = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
search &= Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("name", regex) |
Builders<BsonDocument>.Filter.Regex("type", regex) |
Builders<BsonDocument>.Filter.Regex("filesystem", regex);
}
var query = Database.HostInterface()
.Aggregate()
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.AppendStage<BsonDocument>(new BsonDocument("$addFields", new BsonDocument
{
{ "host", new BsonDocument("$first", "$hosts") }
}))
.AppendStage<BsonDocument>(new BsonDocument("$lookup", new BsonDocument
{
{ "from", "customer" },
{ "localField", "host._customer" },
{ "foreignField", "_id" },
{ "as", "customers" }
}))
.AppendStage<BsonDocument>(new BsonDocument("$addFields", new BsonDocument
{
{ "customer", new BsonDocument("$first", "$customers") }
}))
.Match(search)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Name" => Builders<BsonDocument>.Sort.Ascending("name"),
"Description" => Builders<BsonDocument>.Sort.Ascending("description"),
"Mac" => Builders<BsonDocument>.Sort.Ascending("mac"),
"Suffix" => Builders<BsonDocument>.Sort.Ascending("suffix"),
"Status" => Builders<BsonDocument>.Sort.Ascending("status"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Name" => Builders<BsonDocument>.Sort.Descending("name"),
"Description" => Builders<BsonDocument>.Sort.Descending("description"),
"Mac" => Builders<BsonDocument>.Sort.Descending("mac"),
"Suffix" => Builders<BsonDocument>.Sort.Descending("suffix"),
"Status" => Builders<BsonDocument>.Sort.Descending("status"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonId]
public ObjectId? Id { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
[BsonElement("name")]
public string? Name { get; set; }
[BsonElement("description")]
public string? Description { get; set; }
[BsonElement("mac")]
public string? Mac { get; set; }
[BsonElement("suffix")]
public string? Suffix { get; set; }
[BsonElement("status")]
public string? Status { get; set; }
}
}

View file

@ -0,0 +1,44 @@
@inherits ComponentBase
@using Insight.Web.Extensions
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Interface" T="ViewModel">
Interface
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Interface">
<MudLink Href="@Navigation.Management.Hosts.Network.Interfaces.DetailsHref(@context?.Hosts?.Id, @context?.Interface?.Id.ToString())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Interface?.Name
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,132 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Nameservers;
[Route(Navigation.Inventory.Network.Nameservers.Hosts)]
public partial class Hosts
{
[Parameter] public string? NameserverAddress { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
NameserverAddress = NameserverAddress?.UriEscape(true);
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Nameservers", href: Navigation.Inventory.Network.Nameservers.Index));
if (string.IsNullOrWhiteSpace(NameserverAddress))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Network.Nameservers.Index);
return;
}
Title = $"Inventory » Nameserver » {NameserverAddress} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(NameserverAddress, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("interface", regex);
}
var query = Database.HostInterfaceNameserver()
.Aggregate()
.Match(Builders<HostInterfaceNameserverEntity>.Filter.Eq(p => p.Address, NameserverAddress))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Lookup("host_if", "_interface", "_id", "interfaces")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "interface", new BsonDocument("$first", "$interfaces") },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Interface" => Builders<BsonDocument>.Sort.Ascending("interface.name"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Interface" => Builders<BsonDocument>.Sort.Descending("interface.name"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("interface")]
public HostInterfaceEntity? Interface { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

View file

@ -0,0 +1,32 @@
@inherits ComponentBase
@using Insight.Web.Extensions
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Address" T="ViewModel">
Address
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Hosts" T="ViewModel">
Hosts
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Address">
@context?.Address
</MudTd>
<MudTd DataLabel="Hosts">
<MudLink Href="@Navigation.Inventory.Network.Nameservers.HostsHref(context?.Address?.UriEscape())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts
</MudLink>
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,111 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Nameservers;
[Route(Navigation.Inventory.Network.Nameservers.Index)]
public partial class Index
{
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
Title = $"Inventory » NameserversInsight";
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Nameservers", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<HostInterfaceNameserverEntity>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<HostInterfaceNameserverEntity>.Filter.Regex(x => x.Address, regex);
}
var query = Database.HostInterfaceNameserver()
.Aggregate()
.Match(filter)
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Group(new BsonDocument
{
{ "_id", new BsonDocument("address", "$address") },
{ "address", new BsonDocument("$first", "$address") },
{ "hosts", new BsonDocument("$addToSet", "$hosts") },
})
.Project(new BsonDocument
{
{ "_id", 0 },
{ "address", 1 },
{ "hosts", 1 },
{ "hosts_size", new BsonDocument("$size", "$hosts") },
})
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Address" => Builders<BsonDocument>.Sort.Ascending("address"),
"Hosts" => Builders<BsonDocument>.Sort.Ascending("hosts_size"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Address" => Builders<BsonDocument>.Sort.Descending("address"),
"Hosts" => Builders<BsonDocument>.Sort.Descending("hosts_size"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Descending("hosts_size")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("address")]
public string? Address { get; set; }
[BsonElement("hosts_size")]
public int? Hosts { get; set; }
}
}

View file

@ -0,0 +1,67 @@
@inherits ComponentBase
<TableContainer T="ViewModel"
@ref="Container"
@bind-Search="Search"
Title="@Title"
Breadcrumbs="@Breadcrumbs"
Data="LoadDataAsync">
<Header>
<MudTh>
<MudTableSortLabel SortLabel="Customer" T="ViewModel">
Customer
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Host" T="ViewModel">
Host
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Interface" T="ViewModel">
Interface
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Mask" T="ViewModel">
Mask
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Gateway" T="ViewModel">
Gateway
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortLabel="Metric" T="ViewModel">
Metric
</MudTableSortLabel>
</MudTh>
</Header>
<RowTemplate>
<MudTd DataLabel="Customer">
<MudLink Href="@Navigation.Management.Customers.DetailsHref(@context?.Customers?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Customers?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Host">
<MudLink Href="@Navigation.Management.Hosts.DetailsHref(@context?.Hosts?.Id)" Typo="Typo.inherit" Underline="Underline.None">
@context?.Hosts?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Interface">
<MudLink Href="@Navigation.Management.Hosts.Network.Interfaces.DetailsHref(@context?.Hosts?.Id, @context?.Interface?.Id.ToString())" Typo="Typo.inherit" Underline="Underline.None">
@context?.Interface?.Name
</MudLink>
</MudTd>
<MudTd DataLabel="Mask">
@context?.Mask
</MudTd>
<MudTd DataLabel="Gateway">
@context?.Gateway
</MudTd>
<MudTd DataLabel="Metric">
@context?.Metric
</MudTd>
</RowTemplate>
</TableContainer>

View file

@ -0,0 +1,144 @@
using Insight.Infrastructure;
using Insight.Infrastructure.Entities;
using Insight.Web.Components.Containers;
using Insight.Web.Constants;
using Insight.Web.Extensions;
using Microsoft.AspNetCore.Components;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MudBlazor;
using System.Text.RegularExpressions;
using SortDirection = MudBlazor.SortDirection;
namespace Insight.Web.Pages.Inventory.Network.Routes;
[Route(Navigation.Inventory.Network.Routes.Hosts)]
public partial class Hosts
{
[Parameter] public string? RouteAddress { get; set; }
[Inject] private IMongoDatabase Database { get; init; } = default!;
[Inject] private ISnackbar Snackbar { get; init; } = default!;
[Inject] private NavigationManager NavigationManager { get; init; } = default!;
private TableContainer<ViewModel>? Container { get; set; }
private string Title { get; set; } = Global.Name;
private List<BreadcrumbItem> Breadcrumbs { get; } = new();
private string? Search { get; set; }
protected override void OnInitialized()
{
RouteAddress = RouteAddress?.UriEscape(true);
Breadcrumbs.Add(new BreadcrumbItem("Home", href: Navigation.Home));
Breadcrumbs.Add(new BreadcrumbItem("Inventory", href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Routes", href: Navigation.Inventory.Network.Routes.Index));
if (string.IsNullOrWhiteSpace(RouteAddress))
{
Notification.Error(Snackbar, "Not Found");
NavigationManager.NavigateTo(Navigation.Inventory.Network.Routes.Index);
return;
}
Title = $"Inventory » Route » {RouteAddress} » HostsInsight";
Breadcrumbs.Add(new BreadcrumbItem(RouteAddress, href: "#", true));
Breadcrumbs.Add(new BreadcrumbItem("Hosts", href: "#", true));
}
private async Task<TableData<ViewModel>> LoadDataAsync(TableState state)
{
try
{
var filter = Builders<BsonDocument>.Filter.Empty;
if (string.IsNullOrWhiteSpace(Search) is false)
{
var regex = new BsonRegularExpression(new Regex(Search, RegexOptions.IgnoreCase));
filter &= Builders<BsonDocument>.Filter.Regex("customer.name", regex) |
Builders<BsonDocument>.Filter.Regex("host.name", regex) |
Builders<BsonDocument>.Filter.Regex("interface", regex);
}
var query = Database.HostInterfaceRoute()
.Aggregate()
.Match(Builders<HostInterfaceRouteEntity>.Filter.Eq(p => p.Destination, RouteAddress))
.Lookup("host", "_host", "_id", "hosts")
.Match(new BsonDocument("hosts", new BsonDocument
{
{ "$exists", true },
{ "$ne", new BsonArray() }
}))
.Lookup("customer", "hosts._customer", "_id", "customers")
.Lookup("host_if", "_interface", "_id", "interfaces")
.Project(new BsonDocument
{
{ "_id", 0 },
{ "gateway", 1 },
{ "mask", 1 },
{ "metric", 1 },
{ "interface", new BsonDocument("$first", "$interfaces") },
{ "host", new BsonDocument("$first", "$hosts") },
{ "customer", new BsonDocument("$first", "$customers") },
})
.Match(filter)
.Sort(state.SortDirection switch
{
SortDirection.Ascending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Ascending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Ascending("host.name"),
"Interface" => Builders<BsonDocument>.Sort.Ascending("interface.name"),
_ => null
},
SortDirection.Descending => state.SortLabel switch
{
"Customer" => Builders<BsonDocument>.Sort.Descending("customer.name"),
"Host" => Builders<BsonDocument>.Sort.Descending("host.name"),
"Interface" => Builders<BsonDocument>.Sort.Descending("interface.name"),
_ => null
},
_ => Builders<BsonDocument>.Sort.Ascending("customer.name")
});
var countResult = await query.Count().FirstOrDefaultAsync(default);
var itemResult = await query.Skip(state.Page * state.PageSize).Limit(state.PageSize).ToListAsync(default);
return new TableData<ViewModel>()
{
TotalItems = countResult is null ? 0 : (int)countResult.Count,
Items = itemResult.Select(x => BsonSerializer.Deserialize<ViewModel>(x))
};
}
catch (Exception)
{
Notification.Error(Snackbar);
return new TableData<ViewModel>();
}
}
[BsonIgnoreExtraElements]
public class ViewModel
{
[BsonElement("gateway")]
public string? Gateway { get; set; }
[BsonElement("mask")]
public string? Mask { get; set; }
[BsonElement("metric")]
public int? Metric { get; set; }
[BsonElement("interface")]
public HostInterfaceEntity? Interface { get; set; }
[BsonElement("host")]
public HostEntity? Hosts { get; set; }
[BsonElement("customer")]
public CustomerEntity? Customers { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show more