mirror of
https://github.com/Stefan-5422/-T5-Elektrifikatsiya.git
synced 2026-09-10 07:36:02 +02:00
Merge remote-tracking branch 'origin/Development' into feature-DeviceRegistration
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<Blazorise.ThemeProvider Theme="@theme">
|
@using Blazorise.Components
|
||||||
|
<Blazorise.ThemeProvider Theme="@theme">
|
||||||
<Router AppAssembly="typeof(App).Assembly">
|
<Router AppAssembly="typeof(App).Assembly">
|
||||||
<Found Context="routeData">
|
<Found Context="routeData">
|
||||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Elektrifikatsiya.Layouts.MainLayout)" />
|
<RouteView RouteData="routeData" DefaultLayout="typeof(Elektrifikatsiya.Layouts.MainLayout)" />
|
||||||
@@ -16,11 +17,21 @@
|
|||||||
{
|
{
|
||||||
BarOptions = new()
|
BarOptions = new()
|
||||||
{
|
{
|
||||||
HorizontalHeight = "72px"
|
HorizontalHeight = "72px",
|
||||||
|
DarkColors = new()
|
||||||
|
{
|
||||||
|
ItemColorOptions = new()
|
||||||
|
{
|
||||||
|
ActiveBackgroundColor = "#1a237e",
|
||||||
|
ActiveColor = "#1a237e",
|
||||||
|
HoverColor = "#1a237e",
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ColorOptions = new()
|
ColorOptions = new()
|
||||||
{
|
{
|
||||||
Primary = "#0288D1",
|
Primary = "#1a237e",
|
||||||
Secondary = "#A65529",
|
Secondary = "#A65529",
|
||||||
Success = "#23C02E",
|
Success = "#23C02E",
|
||||||
Info = "#9BD8FE",
|
Info = "#9BD8FE",
|
||||||
@@ -31,7 +42,7 @@
|
|||||||
},
|
},
|
||||||
BackgroundOptions = new()
|
BackgroundOptions = new()
|
||||||
{
|
{
|
||||||
Primary = "#0288D1",
|
Primary = "#1a237e",
|
||||||
Secondary = "#A65529",
|
Secondary = "#A65529",
|
||||||
Success = "#23C02E",
|
Success = "#23C02E",
|
||||||
Info = "#9BD8FE",
|
Info = "#9BD8FE",
|
||||||
@@ -42,7 +53,12 @@
|
|||||||
},
|
},
|
||||||
InputOptions = new()
|
InputOptions = new()
|
||||||
{
|
{
|
||||||
CheckColor = "#0288D1",
|
CheckColor = "#1a237e",
|
||||||
|
},
|
||||||
|
TextColorOptions = new()
|
||||||
|
{
|
||||||
|
Dark = "#000000",
|
||||||
|
Primary = "#1a237e"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<Bar Breakpoint="Breakpoint.Desktop" NavigationBreakpoint="Breakpoint.Tablet" ThemeContrast="ThemeContrast.Dark"
|
<Bar Breakpoint="Breakpoint.Desktop" NavigationBreakpoint="Breakpoint.Tablet" ThemeContrast="ThemeContrast.Dark"
|
||||||
Mode="BarMode.VerticalInline" CollapseMode="BarCollapseMode.Small">
|
Mode="BarMode.VerticalInline" CollapseMode="BarCollapseMode.Small">
|
||||||
<BarToggler />
|
<BarToggler TextColor="TextColor.Primary" />
|
||||||
<BarBrand>
|
<BarBrand>
|
||||||
<BarItem>
|
<BarItem>
|
||||||
<BarLink To="">
|
<BarLink To="">
|
||||||
@@ -12,36 +12,30 @@
|
|||||||
<BarMenu>
|
<BarMenu>
|
||||||
<BarStart>
|
<BarStart>
|
||||||
<BarItem>
|
<BarItem>
|
||||||
<BarLink To="/">
|
<BarLink To="/" TextColor="TextColor.White">
|
||||||
<BarIcon IconName="IconName.Dashboard" />
|
<BarIcon Style="color:white" IconName="IconName.Dashboard" />
|
||||||
Dashboard
|
Dashboard
|
||||||
</BarLink>
|
</BarLink>
|
||||||
</BarItem>
|
</BarItem>
|
||||||
<BarItem>
|
<BarItem>
|
||||||
<BarDropdown @bind-Visible="pagesBarVisible">
|
<BarLink To="/" TextColor="TextColor.White">
|
||||||
<BarDropdownToggle>
|
<BarIcon Style="color:white" IconName="IconName.ShieldAlt" />
|
||||||
<BarIcon IconName="IconName.Edit" />
|
Admin
|
||||||
Pages
|
</BarLink>
|
||||||
</BarDropdownToggle>
|
</BarItem>
|
||||||
<BarDropdownMenu>
|
<BarItem>
|
||||||
<BarDropdownItem To="/simple-form">Simple Form</BarDropdownItem>
|
<BarLink To="/" TextColor="TextColor.White">
|
||||||
</BarDropdownMenu>
|
<BarIcon Style="color:white" IconName="IconName.Plug" />
|
||||||
</BarDropdown>
|
Add Plug
|
||||||
|
</BarLink>
|
||||||
</BarItem>
|
</BarItem>
|
||||||
</BarStart>
|
</BarStart>
|
||||||
<BarEnd>
|
<BarEnd>
|
||||||
<BarItem>
|
<BarItem>
|
||||||
<BarDropdown>
|
<BarLink To="/" TextColor="TextColor.White">
|
||||||
<BarDropdownToggle>
|
<BarIcon Style="color:white" IconName="IconName.Wrench" />
|
||||||
<BarIcon IconName="IconName.Smartphone" />
|
Settings
|
||||||
Apps
|
</BarLink>
|
||||||
</BarDropdownToggle>
|
|
||||||
<BarDropdownMenu>
|
|
||||||
<BarDropdownItem To="/apps/todo">
|
|
||||||
Todo
|
|
||||||
</BarDropdownItem>
|
|
||||||
</BarDropdownMenu>
|
|
||||||
</BarDropdown>
|
|
||||||
</BarItem>
|
</BarItem>
|
||||||
</BarEnd>
|
</BarEnd>
|
||||||
</BarMenu>
|
</BarMenu>
|
||||||
@@ -49,5 +43,6 @@
|
|||||||
@code {
|
@code {
|
||||||
private bool pagesBarVisible = true;
|
private bool pagesBarVisible = true;
|
||||||
|
|
||||||
RenderFragment customIcon =@<img src="/brand-logo.png" style="width:32px; height: 32px" />;
|
//TODO: replace logo png with transparent background one
|
||||||
|
RenderFragment customIcon =@<img src="/logo.png" style="width:32px; height: 32px" />;
|
||||||
}
|
}
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
@using Blazorise.Localization
|
|
||||||
|
|
||||||
<Bar @bind-Visible="@topbarVisible" Breakpoint="Breakpoint.Desktop" Background="Background.Primary" ThemeContrast="ThemeContrast.Light">
|
|
||||||
<BarBrand>
|
|
||||||
<BarItem>
|
|
||||||
<BarLink To="">
|
|
||||||
<BarIcon Margin="Margin.Is2.FromEnd" IconName="IconName.Dashboard" />
|
|
||||||
Elektrifikatsiya
|
|
||||||
</BarLink>
|
|
||||||
</BarItem>
|
|
||||||
</BarBrand>
|
|
||||||
<BarToggler />
|
|
||||||
<BarMenu>
|
|
||||||
<BarStart>
|
|
||||||
<BarItem>
|
|
||||||
<BarLink To="/">Home</BarLink>
|
|
||||||
</BarItem>
|
|
||||||
<BarItem>
|
|
||||||
<BarLink To="https://blazorise.com/docs/">Documentation</BarLink>
|
|
||||||
</BarItem>
|
|
||||||
<BarItem>
|
|
||||||
<BarDropdown>
|
|
||||||
<BarDropdownToggle>More</BarDropdownToggle>
|
|
||||||
<BarDropdownMenu>
|
|
||||||
<BarDropdownItem To="https://blazorise.com/docs/start/">
|
|
||||||
Quick-Start Guide
|
|
||||||
</BarDropdownItem>
|
|
||||||
<BarDropdownDivider />
|
|
||||||
<BarDropdownItem To="https://blazorise.com/docs/usage/">
|
|
||||||
Usage
|
|
||||||
</BarDropdownItem>
|
|
||||||
</BarDropdownMenu>
|
|
||||||
</BarDropdown>
|
|
||||||
</BarItem>
|
|
||||||
<BarItem>
|
|
||||||
<BarDropdown>
|
|
||||||
<BarDropdownToggle>Layout</BarDropdownToggle>
|
|
||||||
<BarDropdownMenu>
|
|
||||||
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("fixed-header"))">
|
|
||||||
@if ( LayoutType == "fixed-header" )
|
|
||||||
{
|
|
||||||
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
|
|
||||||
}
|
|
||||||
Fixed Header
|
|
||||||
</BarDropdownItem>
|
|
||||||
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("fixed-header-footer-only"))">
|
|
||||||
@if ( LayoutType == "fixed-header-footer-only" )
|
|
||||||
{
|
|
||||||
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
|
|
||||||
}
|
|
||||||
Fixed Header and Footer only
|
|
||||||
</BarDropdownItem>
|
|
||||||
<BarDropdownItem Clicked="@(()=>OnLayoutTypeChecked("sider-with-header-on-top"))">
|
|
||||||
@if ( LayoutType == "sider-with-header-on-top" )
|
|
||||||
{
|
|
||||||
<Icon Name="IconName.CheckCircle" TextColor="TextColor.Success" />
|
|
||||||
}
|
|
||||||
Sider with Header on top
|
|
||||||
</BarDropdownItem>
|
|
||||||
</BarDropdownMenu>
|
|
||||||
</BarDropdown>
|
|
||||||
</BarItem>
|
|
||||||
|
|
||||||
|
|
||||||
</BarStart>
|
|
||||||
<BarEnd>
|
|
||||||
<BarItem>
|
|
||||||
<BarDropdown RightAligned>
|
|
||||||
<BarDropdownToggle><Icon Name="IconName.Language" /></BarDropdownToggle>
|
|
||||||
<BarDropdownMenu>
|
|
||||||
@foreach ( var cultureInfo in LocalizationService.AvailableCultures )
|
|
||||||
{
|
|
||||||
<BarDropdownItem @key="@cultureInfo.Name" Clicked="@(()=>SelectCulture(cultureInfo.Name))">
|
|
||||||
@if ( cultureInfo.IsNeutralCulture )
|
|
||||||
{
|
|
||||||
@cultureInfo.EnglishName
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@cultureInfo.Parent.EnglishName
|
|
||||||
}
|
|
||||||
</BarDropdownItem>
|
|
||||||
}
|
|
||||||
</BarDropdownMenu>
|
|
||||||
</BarDropdown>
|
|
||||||
</BarItem>
|
|
||||||
<BarItem>
|
|
||||||
<BarDropdown RightAligned>
|
|
||||||
<BarDropdownToggle><Icon Name="IconName.Tint" /> Theme</BarDropdownToggle>
|
|
||||||
<BarDropdownMenu Style="padding: 15px; min-width:550px;">
|
|
||||||
<Row>
|
|
||||||
<Column Margin="Margin.Is2.FromBottom">
|
|
||||||
<Field>
|
|
||||||
<Switch TValue="bool" Checked="@(Theme?.Enabled == true)" CheckedChanged="@ThemeEnabledChanged">Theme enabled</Switch>
|
|
||||||
</Field>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Column Margin="Margin.Is2.FromBottom">
|
|
||||||
<Field>
|
|
||||||
<Check TValue="bool" Checked="@(Theme?.IsGradient == true)" CheckedChanged="@ThemeGradientChanged">Gradient colors</Check>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<Check TValue="bool" Checked="@(Theme?.IsRounded == true)" CheckedChanged="@ThemeRoundedChanged">Rounded elements</Check>
|
|
||||||
</Field>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Column>
|
|
||||||
<Container Fluid>
|
|
||||||
<ThemeColorSelector Value="@(Theme?.ColorOptions?.Primary)" ValueChanged="@ThemeColorChanged"></ThemeColorSelector>
|
|
||||||
</Container>
|
|
||||||
</Column>
|
|
||||||
</Row>
|
|
||||||
</BarDropdownMenu>
|
|
||||||
</BarDropdown>
|
|
||||||
</BarItem>
|
|
||||||
<BarItem>
|
|
||||||
<BarLink To="https://github.com/Megabit/Blazorise">GitHub</BarLink>
|
|
||||||
</BarItem>
|
|
||||||
</BarEnd>
|
|
||||||
</BarMenu>
|
|
||||||
</Bar>
|
|
||||||
@code {
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await SelectCulture( "en-US" );
|
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
Task SelectCulture( string name )
|
|
||||||
{
|
|
||||||
LocalizationService.ChangeLanguage( name );
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool topbarVisible = false;
|
|
||||||
|
|
||||||
Task OnLayoutTypeChecked( string layoutType )
|
|
||||||
{
|
|
||||||
LayoutType = layoutType;
|
|
||||||
|
|
||||||
return LayoutTypeChanged.InvokeAsync( layoutType );
|
|
||||||
}
|
|
||||||
|
|
||||||
[Parameter] public EventCallback<bool> ThemeEnabledChanged { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public EventCallback<bool> ThemeGradientChanged { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public EventCallback<bool> ThemeRoundedChanged { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public EventCallback<string> ThemeColorChanged { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public string LayoutType { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public EventCallback<string> LayoutTypeChanged { get; set; }
|
|
||||||
|
|
||||||
[Inject] protected ITextLocalizerService LocalizationService { get; set; }
|
|
||||||
|
|
||||||
[CascadingParameter] protected Theme Theme { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Elektrifikatsiya.Model;
|
using Elektrifikatsiya.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Elektrifikatsiya.Database
|
namespace Elektrifikatsiya.Database
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Database;
|
||||||
|
|
||||||
|
public class UserDatabaseContext : DbContext
|
||||||
|
{
|
||||||
|
public UserDatabaseContext(DbContextOptions<UserDatabaseContext> dbContextOptions) : base(dbContextOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<User> Users { get; set; }
|
||||||
|
}
|
||||||
@@ -18,16 +18,27 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazorise" Version="1.1.*" />
|
<Compile Remove="Model\**" />
|
||||||
<PackageReference Include="Blazorise.Material" Version="1.1.*" />
|
<Content Remove="Model\**" />
|
||||||
<PackageReference Include="Blazorise.Icons.Material" Version="1.1.*" />
|
<EmbeddedResource Remove="Model\**" />
|
||||||
|
<None Remove="Model\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Blazorise" Version="1.2.0" />
|
||||||
|
<PackageReference Include="Blazorise.Charts" Version="1.2.0" />
|
||||||
|
<PackageReference Include="Blazorise.Components" Version="1.2.0" />
|
||||||
|
<PackageReference Include="Blazorise.Material" Version="1.2.0" />
|
||||||
|
<PackageReference Include="Blazorise.Icons.Material" Version="1.2.0" />
|
||||||
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
|
<PackageReference Include="FluentResults" Version="3.15.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Model\" />
|
<Folder Include="Middleware\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,57 +2,8 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
|
|
||||||
@if ( layoutType == "fixed-header" )
|
|
||||||
{
|
<Layout Sider>
|
||||||
<Layout Sider>
|
|
||||||
<LayoutSider>
|
|
||||||
<LayoutSiderContent>
|
|
||||||
<SideMenu />
|
|
||||||
</LayoutSiderContent>
|
|
||||||
</LayoutSider>
|
|
||||||
<Layout>
|
|
||||||
<LayoutHeader Fixed>
|
|
||||||
<TopMenu ThemeEnabledChanged="@OnThemeEnabledChanged"
|
|
||||||
ThemeGradientChanged="@OnThemeGradientChanged"
|
|
||||||
ThemeRoundedChanged="@OnThemeRoundedChanged"
|
|
||||||
ThemeColorChanged="@OnThemeColorChanged"
|
|
||||||
@bind-LayoutType="@layoutType" />
|
|
||||||
</LayoutHeader>
|
|
||||||
<LayoutContent Padding="Padding.Is4.OnX.Is4.FromTop">
|
|
||||||
@Body
|
|
||||||
</LayoutContent>
|
|
||||||
</Layout>
|
|
||||||
</Layout>
|
|
||||||
}
|
|
||||||
else if ( layoutType == "fixed-header-footer-only" )
|
|
||||||
{
|
|
||||||
<Layout>
|
|
||||||
<LayoutHeader Fixed>
|
|
||||||
<TopMenu ThemeEnabledChanged="@OnThemeEnabledChanged"
|
|
||||||
ThemeGradientChanged="@OnThemeGradientChanged"
|
|
||||||
ThemeRoundedChanged="@OnThemeRoundedChanged"
|
|
||||||
ThemeColorChanged="@OnThemeColorChanged"
|
|
||||||
@bind-LayoutType="@layoutType" />
|
|
||||||
</LayoutHeader>
|
|
||||||
<LayoutContent Padding="Padding.Is4.OnX.Is4.FromTop">
|
|
||||||
@Body
|
|
||||||
</LayoutContent>
|
|
||||||
<LayoutFooter Fixed>
|
|
||||||
<FooterMenu></FooterMenu>
|
|
||||||
</LayoutFooter>
|
|
||||||
</Layout>
|
|
||||||
}
|
|
||||||
else if ( layoutType == "sider-with-header-on-top" )
|
|
||||||
{
|
|
||||||
<Layout>
|
|
||||||
<LayoutHeader Fixed>
|
|
||||||
<TopMenu ThemeEnabledChanged="@OnThemeEnabledChanged"
|
|
||||||
ThemeGradientChanged="@OnThemeGradientChanged"
|
|
||||||
ThemeRoundedChanged="@OnThemeRoundedChanged"
|
|
||||||
ThemeColorChanged="@OnThemeColorChanged"
|
|
||||||
@bind-LayoutType="@layoutType" />
|
|
||||||
</LayoutHeader>
|
|
||||||
<Layout Sider>
|
|
||||||
<LayoutSider>
|
<LayoutSider>
|
||||||
<LayoutSiderContent>
|
<LayoutSiderContent>
|
||||||
<SideMenu />
|
<SideMenu />
|
||||||
@@ -63,6 +14,4 @@ else if ( layoutType == "sider-with-header-on-top" )
|
|||||||
@Body
|
@Body
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace Elektrifikatsiya.Model;
|
|
||||||
|
|
||||||
public class Device
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public string Key { get; private set; }
|
|
||||||
[Required]
|
|
||||||
public string Name { get; private set; } = string.Empty;
|
|
||||||
[Required]
|
|
||||||
public IPAddress Address { get; private set; }
|
|
||||||
|
|
||||||
public Device(string key, IPAddress address)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
Address = address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
public class Device
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public string Key { get; private set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public IPAddress Address { get; private set; }
|
||||||
|
|
||||||
|
public int PowerUsage { get; set; }
|
||||||
|
public string User { get; set; }
|
||||||
|
public string Room { get; set; }
|
||||||
|
|
||||||
|
public Device(string key, string name, IPAddress address, int powerUsage, string user, string room)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Name = name;
|
||||||
|
Address = address;
|
||||||
|
PowerUsage = powerUsage;
|
||||||
|
User = user;
|
||||||
|
Room = room;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Elektrifikatsiya.Models
|
||||||
|
{
|
||||||
|
public class Event
|
||||||
|
{
|
||||||
|
public string EventName { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
|
||||||
|
//if Plug Class implemented -> Property for which plug this event is from
|
||||||
|
|
||||||
|
public Event(string eventName, string description, DateTime date)
|
||||||
|
{
|
||||||
|
EventName = eventName;
|
||||||
|
Description = description;
|
||||||
|
Date = date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
// Higher up = Higher permission
|
||||||
|
// Or you can set the priority manually <Role> = <value>
|
||||||
|
public enum Role
|
||||||
|
{
|
||||||
|
Admin,
|
||||||
|
User
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
[Key]
|
||||||
|
public int Id { get; private set; }
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
public string PasswordHash { get; private set; }
|
||||||
|
public string? SessionToken { get; set; }
|
||||||
|
public DateTime LastLoginDate { get; set; }
|
||||||
|
public Role Role { get; set; }
|
||||||
|
|
||||||
|
public User(string name, string passwordHash, Role role)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
PasswordHash = passwordHash;
|
||||||
|
Role = role;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,122 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using Blazorise.Components;
|
||||||
|
@using Elektrifikatsiya.Models;
|
||||||
@inject IVersionProvider VersionProvider
|
@inject IVersionProvider VersionProvider
|
||||||
<Heading Size="HeadingSize.Is1" Margin="Margin.Is3.FromBottom">Blazorise</Heading>
|
|
||||||
|
|
||||||
<Paragraph>
|
<Row Padding="Padding.Is3">
|
||||||
Blazorise is a component library built on top of Blazor and CSS frameworks like <Anchor To="https://getbootstrap.com/" Target="Target.Blank">Bootstrap</Anchor>, <Anchor To="https://bulma.io/" Target="Target.Blank">Bulma</Anchor>, <Anchor To="https://ant.design/" Target="Target.Blank">Ant Design</Anchor>, and <Anchor To="http://daemonite.github.io/material/" Target="Target.Blank">Material</Anchor>. It can be used to build responsive, single-page web applications.
|
<Column ColumnSize="ColumnSize.Is6">
|
||||||
</Paragraph>
|
<ListView TItem="Device"
|
||||||
|
Data="@plugs"
|
||||||
|
TextField="@((_)=>(""))"
|
||||||
|
ValueField="@((_)=>(""))"
|
||||||
|
Mode="ListGroupMode.Static">
|
||||||
|
<ItemTemplate>
|
||||||
|
<ListGroupItem>
|
||||||
|
<Row>
|
||||||
|
<Column ColumnSize="ColumnSize.Is8">
|
||||||
|
<Row>
|
||||||
|
<Column ColumnSize="ColumnSize.Is7">
|
||||||
|
<Heading Size="HeadingSize.Is6" Margin="Margin.Is1.FromBottom">@context.Item.Name</Heading>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Column>
|
||||||
|
<Small>User: @context.Item.User</Small>
|
||||||
|
<br />
|
||||||
|
<Small>Room: @context.Item.Room</Small>
|
||||||
|
</Column>
|
||||||
|
<Column ColumnSize="ColumnSize.Is5">
|
||||||
|
<Paragraph Margin="Margin.Is1.OnY">@context.Item.PowerUsage W</Paragraph>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
<Column ColumnSize="ColumnSize.Is4">
|
||||||
|
<Button Position="Position.Absolute.Top.Is50.Start.Is50.Translate.Middle" Color="Color.Dark" Outline>
|
||||||
|
<BarIcon IconName="IconName.Bolt" />
|
||||||
|
</Button>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
</ListGroupItem>
|
||||||
|
</ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</Column>
|
||||||
|
<Column ColumnSize="ColumnSize.Is6">
|
||||||
|
<ListView TItem="Event"
|
||||||
|
Data="@events"
|
||||||
|
TextField="@((_)=>(""))"
|
||||||
|
ValueField="@((_)=>(""))"
|
||||||
|
Mode="ListGroupMode.Static">
|
||||||
|
<ItemTemplate>
|
||||||
|
<Div Flex="Flex.InlineFlex.JustifyContent.Between" Width="Width.Is100">
|
||||||
|
<Heading Size="HeadingSize.Is6" Margin="Margin.Is2.FromBottom">@context.Item.EventName</Heading>
|
||||||
|
<Small>@context.Item.Date</Small>
|
||||||
|
</Div>
|
||||||
|
<Paragraph Margin="Margin.Is2.FromBottom">@context.Item.Description</Paragraph>
|
||||||
|
</ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
<Row Padding="Padding.Is3">
|
||||||
|
<Column>
|
||||||
|
<Button Color="Color.Primary" Clicked="@(async () => await HandleRedraw())">Aktualisieren</Button>
|
||||||
|
<LineChart @ref="lineChart" TItem="double" />
|
||||||
|
</Column>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Alert Color="Color.Info" Visible>
|
@code {
|
||||||
<Paragraph>
|
//TODO: insert new event here if plug produces one
|
||||||
This is a Blazorise Starting Template allowing you to quickly get started building your project!
|
List<Event> events = new List<Event>() { new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now), new Event("Placeholder", "Placeholder", DateTime.Now) };
|
||||||
</Paragraph>
|
//TODO: insert new Device here if user adds one
|
||||||
|
List<Device> plugs = new List<Device>() { new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF"), new Device("Placeholder", "Plug", System.Net.IPAddress.Broadcast, 60, "Oldin", "4AHINF") };
|
||||||
|
|
||||||
<Paragraph>
|
//code for graph
|
||||||
The bare minimum has been installed for you:
|
LineChart<double> lineChart;
|
||||||
<ul>
|
|
||||||
<li>Blazorise @($"v{VersionProvider.MilestoneVersion}")</li>
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
<li>The Blazorise Provider you have selected</li>
|
{
|
||||||
<li>The corresponding icon provider</li>
|
if (firstRender)
|
||||||
</ul>
|
{
|
||||||
However Blazorise has many more extensions at your disposal.
|
await HandleRedraw();
|
||||||
You can find them <Anchor To="https://blazorise.com/docs/extensions" Target="Target.Blank">here</Anchor>.
|
}
|
||||||
</Paragraph>
|
}
|
||||||
|
|
||||||
|
async Task HandleRedraw()
|
||||||
|
{
|
||||||
|
await lineChart.Clear();
|
||||||
|
|
||||||
|
await lineChart.AddLabelsDatasetsAndUpdate(Labels, GetLineChartDataset());
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: insert dataset of current and last voltage usages
|
||||||
|
LineChartDataset<double> GetLineChartDataset()
|
||||||
|
{
|
||||||
|
return new LineChartDataset<double>
|
||||||
|
{
|
||||||
|
Label = "# of random number in thousands",
|
||||||
|
Data = RandomizeData(),
|
||||||
|
BackgroundColor = backgroundColors,
|
||||||
|
BorderColor = borderColors,
|
||||||
|
Fill = true,
|
||||||
|
PointRadius = 3,
|
||||||
|
CubicInterpolationMode = "monotone",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] Labels = { "Red", "Blue", "Yellow", "Green", "Purple", "Orange" };
|
||||||
|
List<string> backgroundColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 0.2f), ChartColor.FromRgba(54, 162, 235, 0.2f), ChartColor.FromRgba(255, 206, 86, 0.2f), ChartColor.FromRgba(75, 192, 192, 0.2f), ChartColor.FromRgba(153, 102, 255, 0.2f), ChartColor.FromRgba(255, 159, 64, 0.2f) };
|
||||||
|
List<string> borderColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 1f), ChartColor.FromRgba(54, 162, 235, 1f), ChartColor.FromRgba(255, 206, 86, 1f), ChartColor.FromRgba(75, 192, 192, 1f), ChartColor.FromRgba(153, 102, 255, 1f), ChartColor.FromRgba(255, 159, 64, 1f) };
|
||||||
|
|
||||||
|
List<double> RandomizeData()
|
||||||
|
{
|
||||||
|
var r = new Random(DateTime.Now.Millisecond);
|
||||||
|
|
||||||
|
return new List<double> {
|
||||||
|
r.Next( 3, 50 ) * r.NextDouble(),
|
||||||
|
r.Next( 3, 50 ) * r.NextDouble(),
|
||||||
|
r.Next( 3, 50 ) * r.NextDouble(),
|
||||||
|
r.Next( 3, 50 ) * r.NextDouble(),
|
||||||
|
r.Next( 3, 50 ) * r.NextDouble(),
|
||||||
|
r.Next( 3, 50 ) * r.NextDouble() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<Paragraph>
|
|
||||||
Please visit the official <Anchor To="https://bootstrapdemo.blazorise.com/" Target="Target.Blank">Blazorise Demo</Anchor> for component examples.
|
|
||||||
</Paragraph>
|
|
||||||
<Paragraph>
|
|
||||||
Please visit the official <Anchor To="https://blazorise.com/docs/" Target="Target.Blank">Blazorise Documentation</Anchor> to learn more about the available components.
|
|
||||||
</Paragraph>
|
|
||||||
</Alert>
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using Microsoft.AspNetCore.Components.Web.Virtualization;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Elektrifikatsiya;
|
||||||
|
using Blazorise;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Pages
|
||||||
|
{
|
||||||
|
public partial class Dashboard
|
||||||
|
{
|
||||||
|
string bgcolor { get; set; } = "00f"; // (starting value)
|
||||||
|
void SetColor()
|
||||||
|
{
|
||||||
|
bgcolor = "#fd7";
|
||||||
|
StateHasChanged(); // may not be required, but I'm at work right now, so can't check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,5 +40,35 @@
|
|||||||
|
|
||||||
<script src="js/material/material.min.js"></script>
|
<script src="js/material/material.min.js"></script>
|
||||||
<script src="_framework/blazor.server.js"></script>
|
<script src="_framework/blazor.server.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.blazorExtensions = {
|
||||||
|
WriteCookie: function(name, value, days) {
|
||||||
|
|
||||||
|
var expires;
|
||||||
|
if (days) {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toGMTString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
expires = "";
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + value + expires + "; path=/;SameSite=Strict";
|
||||||
|
},
|
||||||
|
|
||||||
|
ScrollToId: function(id) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element instanceof HTMLElement) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "start",
|
||||||
|
inline: "nearest"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
using Blazorise;
|
using Blazorise;
|
||||||
|
using Blazorise.Bootstrap;
|
||||||
using Blazorise.Icons.Material;
|
using Blazorise.Icons.Material;
|
||||||
using Blazorise.Material;
|
using Blazorise.Material;
|
||||||
using Elektrifikatsiya.Database;
|
using Elektrifikatsiya.Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
using Elektrifikatsiya.Database;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
|
builder.Services.AddDbContext<UserDatabaseContext>(options => options.UseSqlite("Data Source=./UserDatabase.sqlite"));
|
||||||
builder.Services.AddDbContext<DeviceManagmentDatabaseContext>((options)=>options.UseSqlite("Data Source=./DeviceManagement.sqlite"));
|
builder.Services.AddDbContext<DeviceManagmentDatabaseContext>((options)=>options.UseSqlite("Data Source=./DeviceManagement.sqlite"));
|
||||||
|
|
||||||
AddBlazorise(builder.Services);
|
AddBlazorise(builder.Services);
|
||||||
@@ -28,12 +34,11 @@ app.UseHttpsRedirection();
|
|||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
app.MapBlazorHub();
|
||||||
|
app.MapFallbackToPage("/_Host");
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
IServiceScope serviceScope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||||
{
|
serviceScope.ServiceProvider.GetRequiredService<UserDatabaseContext>().Database.EnsureCreated();
|
||||||
_ = endpoints.MapBlazorHub();
|
|
||||||
_ = endpoints.MapFallbackToPage("/_Host");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
@@ -50,3 +55,9 @@ void AddBlazorise(IServiceCollection services)
|
|||||||
.AddMaterialProviders()
|
.AddMaterialProviders()
|
||||||
.AddMaterialIcons();
|
.AddMaterialIcons();
|
||||||
}
|
}
|
||||||
|
builder.Services
|
||||||
|
.AddBlazorise(options =>
|
||||||
|
{
|
||||||
|
options.Immediate = true;
|
||||||
|
})
|
||||||
|
.AddBootstrapProviders();
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"Docker": {
|
"Docker": {
|
||||||
"commandName": "Docker",
|
"commandName": "Docker",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "{Scheme}://{IPAdress}:{ServicePort}",
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||||
"publishAllPorts": true,
|
"publishAllPorts": true,
|
||||||
"useSSL": true,
|
"useSSL": true,
|
||||||
"DockerfileRunArguments": "-p 80:80 -p 443:443"
|
"DockerfileRunArguments": "-p 80:80 -p 443:443"
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Services;
|
||||||
|
|
||||||
|
public interface IAuthenticationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current user.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> <see cref="Task"/> to <see langword="await"/> and the currently authenticated user.</returns>
|
||||||
|
Task<Result<User>> GetUserAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name address of the user.</param>
|
||||||
|
/// <param name="password">The password of the user.</param>
|
||||||
|
/// <param name="role">The default role of the user.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
|
||||||
|
|
||||||
|
Task<Result> RegisterUserAsync(string name, string password, Role role);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs a user in.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the user.</param>
|
||||||
|
/// <param name="password">The password of the user.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
|
||||||
|
Task<Result> LoginUserAsync(string name, string password);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the user out.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
|
||||||
|
Task<Result> LogoutUserAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the current user.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/>.</returns>
|
||||||
|
Task<Result> DeleteUserAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a user exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name address of the user.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/> and a <see cref="bool"/> that indicates if the user exists.</returns>
|
||||||
|
Task<Result<bool>> UserExistsAsync(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the current user is authenticated.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/> and a <see cref="bool"/> that indicates if the user is authenticated.</returns>
|
||||||
|
Task<Result<bool>> IsAuthenticated();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Services;
|
||||||
|
|
||||||
|
public interface IAuthorizationService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the current user is authorized.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requiredRole">The minimum required role.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> to <see langword="await"/> and a <see cref="bool"/> that indicates if the user is authorized.</returns>
|
||||||
|
Task<Result<bool>> IsAuthorized(Role requiredRole);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Elektrifikatsiya.Services;
|
||||||
|
|
||||||
|
public interface ICookieService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a cookie.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the cookie.</param>
|
||||||
|
/// <param name="value">The value of the cookie.</param>
|
||||||
|
/// <param name="days">Indicates the maximum lifetime of the cookie.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task WriteCookieAsync(string name, string value, int days);
|
||||||
|
}
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
using Elektrifikatsiya.Database;
|
||||||
|
using Elektrifikatsiya.Models;
|
||||||
|
using Elektrifikatsiya.Utilities;
|
||||||
|
|
||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
using BC = BCrypt.Net.BCrypt;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Services.Implementations;
|
||||||
|
|
||||||
|
public class AuthenticationService : IAuthenticationService
|
||||||
|
{
|
||||||
|
private readonly UserDatabaseContext userDatabaseContext;
|
||||||
|
private readonly IHttpContextAccessor httpContextAccessor;
|
||||||
|
private readonly ICookieService cookieService;
|
||||||
|
|
||||||
|
public AuthenticationService(UserDatabaseContext userDatabaseContext, IHttpContextAccessor httpContextAccessor, ICookieService cookieService)
|
||||||
|
{
|
||||||
|
this.userDatabaseContext = userDatabaseContext;
|
||||||
|
this.httpContextAccessor = httpContextAccessor;
|
||||||
|
this.cookieService = cookieService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> DeleteUserAsync()
|
||||||
|
{
|
||||||
|
Result<User> getUserResult = await GetUserAsync();
|
||||||
|
|
||||||
|
if (getUserResult.IsFailed)
|
||||||
|
{
|
||||||
|
return getUserResult.ToResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = userDatabaseContext.Users.Remove(getUserResult.Value);
|
||||||
|
return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<User>> GetUserAsync()
|
||||||
|
{
|
||||||
|
string? token = httpContextAccessor.HttpContext?.Request.Cookies["token"]?.ToString();
|
||||||
|
|
||||||
|
if (!TokenGenerator.ValidateToken(token, "auth"))
|
||||||
|
{
|
||||||
|
return Result.Fail("Token is not valid!");
|
||||||
|
}
|
||||||
|
|
||||||
|
User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token);
|
||||||
|
|
||||||
|
if (user is null || DateTime.UtcNow - user.LastLoginDate > TimeSpan.FromDays(7))
|
||||||
|
{
|
||||||
|
return Result.Fail("Token is not valid or expired!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<bool>> IsAuthenticated()
|
||||||
|
{
|
||||||
|
return (await GetUserAsync()).IsSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> LoginUserAsync(string name, string password)
|
||||||
|
{
|
||||||
|
User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return Result.Fail("User does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!BC.Verify(password, user.PasswordHash))
|
||||||
|
{
|
||||||
|
return Result.Fail("Invalid credentials!");
|
||||||
|
}
|
||||||
|
|
||||||
|
string newToken = TokenGenerator.GenerateToken("auth", user.Id);
|
||||||
|
|
||||||
|
user.LastLoginDate = DateTime.UtcNow;
|
||||||
|
user.SessionToken = newToken;
|
||||||
|
|
||||||
|
_ = await userDatabaseContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
await cookieService.WriteCookieAsync("token", newToken, 7);
|
||||||
|
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> LogoutUserAsync()
|
||||||
|
{
|
||||||
|
string? token = httpContextAccessor.HttpContext?.Request.Cookies["token"]?.ToString();
|
||||||
|
|
||||||
|
await cookieService.WriteCookieAsync("token", string.Empty, 0);
|
||||||
|
|
||||||
|
if (!TokenGenerator.ValidateToken(token, "auth"))
|
||||||
|
{
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
User? user = await userDatabaseContext.Users.FirstOrDefaultAsync(u => u.SessionToken == token);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
user.SessionToken = null;
|
||||||
|
|
||||||
|
_ = await userDatabaseContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Result.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> RegisterUserAsync(string name, string password, Role role)
|
||||||
|
{
|
||||||
|
Result<bool> userExistsResult = await UserExistsAsync(name);
|
||||||
|
|
||||||
|
if (userExistsResult.IsFailed || userExistsResult.Value)
|
||||||
|
{
|
||||||
|
return Result.Fail("User name already exists!");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = new User(name, BC.HashPassword(password), role);
|
||||||
|
_ = userDatabaseContext.Users.Add(user);
|
||||||
|
|
||||||
|
return (await Result.Try(() => userDatabaseContext.SaveChangesAsync())).ToResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Result<bool>> UserExistsAsync(string name)
|
||||||
|
{
|
||||||
|
return Result.Try(() =>
|
||||||
|
userDatabaseContext.Users.AnyAsync(u => u.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)));
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
using Elektrifikatsiya.Models;
|
||||||
|
|
||||||
|
using FluentResults;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Services.Implementations;
|
||||||
|
|
||||||
|
public class AuthorizationService : IAuthorizationService
|
||||||
|
{
|
||||||
|
private readonly IAuthenticationService authenticationService;
|
||||||
|
|
||||||
|
public AuthorizationService(IAuthenticationService authenticationService)
|
||||||
|
{
|
||||||
|
this.authenticationService = authenticationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<bool>> IsAuthorized(Role requiredRole)
|
||||||
|
{
|
||||||
|
Result<User> userResult = await authenticationService.GetUserAsync();
|
||||||
|
|
||||||
|
if (userResult.IsSuccess && userResult.Value.Role <= requiredRole)
|
||||||
|
{
|
||||||
|
return Result.Ok(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Result.Fail("User doesn't have the required role!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Services.Implementations;
|
||||||
|
|
||||||
|
public class CookieService : ICookieService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime jsRuntime;
|
||||||
|
|
||||||
|
public CookieService(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
this.jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteCookieAsync(string name, string value, int days)
|
||||||
|
{
|
||||||
|
_ = await jsRuntime.InvokeAsync<string>("blazorExtensions.WriteCookie", name, value, days);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Elektrifikatsiya.Utilities;
|
||||||
|
|
||||||
|
public static class SecureStringGenerator
|
||||||
|
{
|
||||||
|
public static string CreateCryptographicRandomString(int count)
|
||||||
|
{
|
||||||
|
return Convert.ToBase64String(RandomNumberGenerator.GetBytes(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateCryptographicRandomString(int count, int uid)
|
||||||
|
{
|
||||||
|
List<byte> bytes = RandomNumberGenerator.GetBytes(count).ToList();
|
||||||
|
|
||||||
|
bytes.AddRange(BitConverter.GetBytes(uid));
|
||||||
|
|
||||||
|
return Convert.ToBase64String(bytes.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace Elektrifikatsiya.Utilities;
|
||||||
|
|
||||||
|
public static class TokenGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates secure token and uses an UID to make it unique.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tokenType">The type of the token.</param>
|
||||||
|
/// <param name="uid">The UID of a user.</param>
|
||||||
|
/// <param name="length">The length of the token.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GenerateToken(string tokenType, int uid = 0, int length = 128)
|
||||||
|
{
|
||||||
|
return tokenType + "-" + SecureStringGenerator.CreateCryptographicRandomString(length, uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a token is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">The token to validate.</param>
|
||||||
|
/// <param name="tokenType">The type the token should have.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool ValidateToken(string? token, string tokenType)
|
||||||
|
{
|
||||||
|
if (token is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.StartsWith(tokenType + "-");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,3 +9,4 @@
|
|||||||
|
|
||||||
@using Elektrifikatsiya
|
@using Elektrifikatsiya
|
||||||
@using Blazorise
|
@using Blazorise
|
||||||
|
@using Blazorise.Charts
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
Reference in New Issue
Block a user