alpo8341 7 bulan lalu
induk
melakukan
3433581577

+ 1 - 1
src/DontHarmDesktop/App.config

@@ -8,7 +8,7 @@
     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
   </startup>
   <connectionStrings>
-    <add name="Entities" connectionString="metadata=res://*/Models.DontHarmModel.csdl|res://*/Models.DontHarmModel.ssdl|res://*/Models.DontHarmModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=srv-wsr\is3;initial catalog=user12;user id=user12;password=user12;application name=EntityFramework;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
+    <add name="Entities" connectionString="metadata=res://*/Models.DontHarmModel.csdl|res://*/Models.DontHarmModel.ssdl|res://*/Models.DontHarmModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=srv-wsr\is4;initial catalog=user12;user id=user12;password=is4-user12;application name=EntityFramework;MultipleActiveResultSets=True&quot;" providerName="System.Data.EntityClient" />
   </connectionStrings>
   <entityFramework>
     <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">

+ 0 - 110
src/DontHarmDesktop/App.xaml.cs

@@ -15,115 +15,5 @@ namespace DontHarmDesktop
     /// </summary>
     public partial class App : Application
     {
-        public static Models.Entities Context { get; } = new Models.Entities();
-        public static Models.users CurrentUser = null;
-
-        /// <summary>
-        /// Дата входа пользователя
-        /// </summary>
-        public static DateTime LogOnTime = new DateTime();
-
-        /// <summary>
-        /// Дата конца сеанса (только для лаборанта)
-        /// </summary>
-        public static DateTime LogOffTime = new DateTime();
-
-        /// <summary>
-        /// Заблокирован ли вход в текущий момент?
-        /// </summary>
-        public static bool BlockedLogon = false;
-
-        /// <summary>
-        /// Если вход заблокирован, авторизация запрещена если AllowedLogonTime больше текущего времени
-        /// </summary>
-        public static DateTime AllowedLogonTime;
-
-        /// <summary>
-        /// Число провальных попыток входа, выполненных подряд
-        /// </summary>
-        private static int FailedAttemptsCount = 0;
-
-        public static void BlockLogon(DateTime dateTime)
-        {
-            if (AllowedLogonTime == null || AllowedLogonTime < dateTime)
-            {
-                AllowedLogonTime = dateTime;
-                BlockedLogon = true;
-            }
-        }
-
-        /// <summary>
-        /// Авторизует пользователя
-        /// </summary>
-        /// <param name="login">Логин</param>
-        /// <param name="password">Пароль</param>
-        /// <returns>Успешна ли прошла авторизация</returns>
-        public static bool LogOn(string login, string password)
-        {
-            if (BlockedLogon)
-            {
-                if (DateTime.Now < AllowedLogonTime)
-                {
-                    MessageBox.Show("Вход заблокирован до " + AllowedLogonTime);
-                    AddLogOnRecord(login, false);
-                    return false;
-                } else
-                {
-                    BlockedLogon = false;
-                }
-            }
-            
-            var result = Context.users.FirstOrDefault(p => p.login == login && p.password == password);
-            if (result == null)
-            {
-                MessageBox.Show("Логин или пароль не совпадают");
-                FailedAttemptsCount++;
-
-                if (FailedAttemptsCount == 2)
-                {
-                    BlockLogon(DateTime.Now + TimeSpan.FromSeconds(10));
-                }
-                AddLogOnRecord(login, false);
-                return false;
-            }
-
-            FailedAttemptsCount = 0;
-            CurrentUser = result;
-            LogOnTime = DateTime.Now;
-
-            // Вычисление даты выхода кварцевания (2 минуты)
-            LogOffTime = LogOnTime + TimeSpan.FromMinutes(2);
-
-            // Запуск таймера
-            if (CurrentUser.role == 3 || CurrentUser.role == 4)
-            {
-                (Current.MainWindow as MainWindow).StartLogoffTimer();
-            }
-
-            AddLogOnRecord(login, true);
-            return true;
-        }
-
-        /// <summary>
-        /// Выполняет выход пользователя
-        /// </summary>
-        public static void LogOff()
-        {
-            CurrentUser = null;
-
-            // Тут надо проверить роль пользователя
-            (Current.MainWindow as MainWindow).StopLogoffTimer();
-        }
-
-        private static void AddLogOnRecord(string login, bool successfull)
-        {
-            var record = new login_attempts();
-            record.successfull = Convert.ToByte(successfull);
-            record.login = login;
-            record.ip_address = "127.0.0.1";
-            record.created_at = DateTime.Now;
-            Context.login_attempts.Add(record);
-            Context.SaveChanges();
-        }
     }
 }

+ 62 - 0
src/DontHarmDesktop/AuthState.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DontHarmDesktop.Models;
+
+namespace DontHarmDesktop
+{
+    class AuthState
+    {
+        /// <summary>
+        /// Заблокирован ли логин
+        /// </summary>
+        public static bool LoginBlocked { get; set; } = false;
+
+        /// <summary>
+        /// До какого момента заблокирован логин
+        /// </summary>
+        public static DateTime? LoginBlockedUntil { get; set; } = null;
+
+        /// <summary>
+        /// Сколько раз логин провален
+        /// </summary>
+        public static int AttemptsFailed { get; set; } = 0;
+
+        /// <summary>
+        /// Текущий пользователь
+        /// </summary>
+        public static Models.users CurrentUser { get; set; } = null;
+
+        /// <summary>
+        /// Время начала сеанса
+        /// </summary>
+        public static DateTime SessionStart { get; set; }
+
+        /// <summary>
+        /// Ограничен ли по времени сеанс
+        /// </summary>
+        public static bool SessionIsLimited { get; set; } = false;
+
+        /// <summary>
+        /// Планируемое время завершения сеанса
+        /// </summary>
+        public static DateTime SessionEnd { get; set;}
+
+        /// <summary>
+        /// Блокирует вход в приложение
+        /// </summary>
+        public static void BlockLogin(TimeSpan timeSpan)
+        {
+            DateTime targetTime = DateTime.Now + timeSpan;
+            if (LoginBlockedUntil == null || LoginBlockedUntil < targetTime)
+            {
+                // Если время снятия блокировки больше чем установим сейчас или
+                // совсем не установлено, устанавливаем на заданное
+                LoginBlockedUntil = targetTime;
+                LoginBlocked = true;
+            }
+        }
+    }
+}

+ 23 - 14
src/DontHarmDesktop/DontHarmDesktop.csproj

@@ -41,11 +41,32 @@
     <Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
       <HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
     </Reference>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.1, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.1\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Prism, Version=9.0.537.60525, Culture=neutral, PublicKeyToken=40ee6c3a2184dc59, processorArchitecture=MSIL">
+      <HintPath>..\packages\Prism.Core.9.0.537\lib\net47\Prism.dll</HintPath>
+    </Reference>
+    <Reference Include="Prism.Container.Abstractions, Version=9.0.106.9543, Culture=neutral, PublicKeyToken=40ee6c3a2184dc59, processorArchitecture=MSIL">
+      <HintPath>..\packages\Prism.Container.Abstractions.9.0.106\lib\net47\Prism.Container.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Prism.Events, Version=9.0.537.60525, Culture=neutral, PublicKeyToken=40ee6c3a2184dc59, processorArchitecture=MSIL">
+      <HintPath>..\packages\Prism.Events.9.0.537\lib\net47\Prism.Events.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.ComponentModel.DataAnnotations" />
     <Reference Include="System.Data" />
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
     <Reference Include="System.Runtime.Serialization" />
     <Reference Include="System.Security" />
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
     <Reference Include="System.Xml" />
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Core" />
@@ -64,7 +85,9 @@
       <Generator>MSBuild:Compile</Generator>
       <SubType>Designer</SubType>
     </ApplicationDefinition>
+    <Compile Include="AuthState.cs" />
     <Compile Include="ViewModels\AuthViewModel.cs" />
+    <Compile Include="ViewModels\MainViewModel.cs" />
     <Page Include="Dictionaries\BrushesStyle.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -89,14 +112,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="Pages\FooterPage.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
-    <Page Include="Pages\HeaderPage.xaml">
-      <SubType>Designer</SubType>
-      <Generator>MSBuild:Compile</Generator>
-    </Page>
     <Page Include="Pages\LogOnHistory.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -162,12 +177,6 @@
     <Compile Include="Pages\AuthPage.xaml.cs">
       <DependentUpon>AuthPage.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Pages\FooterPage.xaml.cs">
-      <DependentUpon>FooterPage.xaml</DependentUpon>
-    </Compile>
-    <Compile Include="Pages\HeaderPage.xaml.cs">
-      <DependentUpon>HeaderPage.xaml</DependentUpon>
-    </Compile>
     <Compile Include="Pages\LogOnHistory.xaml.cs">
       <DependentUpon>LogOnHistory.xaml</DependentUpon>
     </Compile>

TEMPAT SAMPAH
src/DontHarmDesktop/Images/Logo.png


+ 46 - 6
src/DontHarmDesktop/MainWindow.xaml

@@ -3,7 +3,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:local="clr-namespace:DontHarmDesktop"
+        xmlns:local="clr-namespace:DontHarmDesktop" xmlns:viewmodels="clr-namespace:DontHarmDesktop.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MainViewModel}"
         mc:Ignorable="d"
         Title="Не навреди"
         Height="600"
@@ -11,11 +11,51 @@
         WindowStartupLocation="CenterScreen">
 
     <Grid>
-        <StackPanel>
-            <Frame x:Name="HeaderFrame" Source="/Pages/HeaderPage.xaml" NavigationUIVisibility="Hidden"></Frame>
-            <Frame x:Name="MainFrame" Source="/Pages/AuthPage.xaml" NavigationUIVisibility="Hidden"></Frame>
-        </StackPanel>
-        <TextBlock x:Name="TimerTextBlock" Visibility="Hidden" VerticalAlignment="Bottom" HorizontalAlignment="Center"></TextBlock>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="64"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="32"/>
+        </Grid.RowDefinitions>
+        
+        <!-- Header start -->
+        <Grid Grid.Row="0" Background="{StaticResource SecondaryColor}">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="64"/>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="128"/>
+            </Grid.ColumnDefinitions>
+            
+            <Image Source="/Pages/Logo.png" Grid.Column="0"/>
+            <TextBlock 
+                Grid.Column="1"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                FontSize="18">Не навреди</TextBlock>
+            <Button
+                Command="{Binding LogoutCmd}"
+                Grid.Column="2"
+                Height="32"
+                Margin="8,0">Выход</Button>
+        </Grid>
+        <!-- Header end -->
+        
+        <!-- Body start -->
+        <Frame 
+            x:Name="MainFrame" 
+            Source="/Pages/AuthPage.xaml"
+            NavigationUIVisibility="Hidden"
+            Grid.Row="1"/>
+        <!-- Body end -->
+        
+        <!-- Footer start -->
+        <Grid Background="{StaticResource SecondaryColor}" Grid.Row="2">
+            <TextBlock 
+                Grid.Row="2"
+                x:Name="TimerTextBlock" 
+                VerticalAlignment="Bottom" 
+                HorizontalAlignment="Left"/>
+        </Grid>
+        <!-- Footer end -->
     </Grid>
     
 </Window>

+ 36 - 25
src/DontHarmDesktop/MainWindow.xaml.cs

@@ -25,16 +25,26 @@ namespace DontHarmDesktop
         /// <summary>
         /// Таймер принуждённого выхода из приложения
         /// </summary>
-        DispatcherTimer logout_timer = new DispatcherTimer();
+        DispatcherTimer logoutTimer = new DispatcherTimer();
+
+        public DispatcherTimer LogoutTimer { get {
+                return logoutTimer;
+            } 
+            set {
+                logoutTimer = value;
+            }
+        }
 
         /// <summary>
         /// Было ли показано предупреждение о скором выходе из системы
         /// </summary>
-        bool shown_logoff_warning = false;
+        bool shownLogoutWarning = false;
 
         public MainWindow()
         {
             InitializeComponent();
+            DataContext = new ViewModels.MainViewModel();
+            logoutTimer.Tick += LogoutTimerTick;
         }
 
         /// <summary>
@@ -42,44 +52,45 @@ namespace DontHarmDesktop
         /// </summary>
         public void StartLogoffTimer()
         {
-            shown_logoff_warning = false;
-            logout_timer.Tick += Logout_timer_Tick;
-            logout_timer.Interval = new TimeSpan(0, 0, 1);
-            logout_timer.Start();
-        }
-
-        public void StopLogoffTimer()
-        {
-            logout_timer.Stop();
-            MainFrame.Navigate(new AuthPage());
-            TimerTextBlock.Text = String.Empty;
+            shownLogoutWarning = false;
+            logoutTimer.Interval = new TimeSpan(0, 0, 5);
+            logoutTimer.Start();
         }
 
         /// <summary>
-        /// callback таймера выхода
+        /// callback таймера принудительного выхода
         /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void Logout_timer_Tick(object sender, EventArgs e)
+        private void LogoutTimerTick(object sender, EventArgs e)
         {
             // Проверяем закончилось ли время
             // Проверку роли не выполняем, т.к. таймер даже не начинается если роль не подходит
-            if (DateTime.Now > App.LogOffTime)
+            if (DateTime.Now > AuthState.SessionEnd)
             {
-                App.BlockLogon(DateTime.Now + TimeSpan.FromMinutes(2));
-                App.LogOff();
+                // Сессия закончилась, блокируем вход на 2 минуты
+                AuthState.BlockLogin(TimeSpan.FromMinutes(2));
+
+                // Останавливаем таймер
+                logoutTimer.Stop();
+
+                // Сбрасываем пользователя
+                AuthState.CurrentUser = null;
+
+                // Отражаем это всё в UI
+                MainFrame.Navigate(new AuthPage());
+                TimerTextBlock.Text = String.Empty;
+                
+                return;
             }
 
             // Вычисление остатка времени
-            var TimeDifference = App.LogOffTime - DateTime.Now;
+            var timeDiff = AuthState.SessionEnd - DateTime.Now;
 
-            if (!shown_logoff_warning && TimeDifference.TotalMinutes < 1)
+            if (!shownLogoutWarning && timeDiff.TotalMinutes < 1)
             {
                 MessageBox.Show("Скоро будет произведено кварцевание! Выход из приложения через 1 минуту");
-                shown_logoff_warning = true;
+                shownLogoutWarning = true;
             }
-
-            TimerTextBlock.Text = "Минут до кварцевания: " + Math.Round(TimeDifference.TotalMinutes, 0);
+            TimerTextBlock.Text = "Минут до кварцевания: " + Math.Round(timeDiff.TotalMinutes, 0);
         }
     }
 }

+ 37 - 19
src/DontHarmDesktop/Pages/AuthPage.xaml

@@ -3,48 +3,66 @@
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:DontHarmDesktop.Pages"
+      xmlns:local="clr-namespace:DontHarmDesktop.Pages" xmlns:viewmodels="clr-namespace:DontHarmDesktop.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:AuthViewModel}"
       mc:Ignorable="d" 
       d:DesignHeight="450" d:DesignWidth="800"
       Title="AuthPage"
       ShowsNavigationUI="False">
 
-    <Page.Resources>
-        <BooleanToVisibilityConverter x:Key="MyConverter"></BooleanToVisibilityConverter>
-    </Page.Resources>
-
     <Grid>
         <Grid.RowDefinitions>
-            <RowDefinition></RowDefinition>
-            <RowDefinition></RowDefinition>
-            <RowDefinition></RowDefinition>
-            <RowDefinition></RowDefinition>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="64"/>
+            <RowDefinition Height="64"/>
+            <RowDefinition Height="64"/>
         </Grid.RowDefinitions>
+
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
         
-        <TextBlock Grid.Row="0" TextAlignment="Center">Добро пожаловать в Не навреди!<LineBreak/>Пожалуйста, авторизуйтесь</TextBlock>
+        <TextBlock
+            Grid.Row="0" 
+            Padding="8"
+            FontSize="18"
+            TextAlignment="Center">
+            Добро пожаловать в Не навреди!<LineBreak/>Пожалуйста, авторизуйтесь
+        </TextBlock>
 
         <Grid Margin="5" Grid.Row="1">
             <Grid.ColumnDefinitions>
                 <ColumnDefinition Width="*"/>
                 <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
-            <Label HorizontalContentAlignment="Right" Grid.Column="0">Логин</Label>
-            <TextBox Grid.Column="1" x:Name="LoginTextBox"/>
+            <Label 
+                HorizontalContentAlignment="Right" 
+                Grid.Column="0"
+                VerticalAlignment="Center">
+                Логин
+            </Label>
+            <TextBox
+                VerticalContentAlignment="Center"
+                Grid.Column="1" Text="{Binding Login}"/>
         </Grid>
 
         <Grid Margin="5" Grid.Row="2">
             <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="2*"/>
                 <ColumnDefinition Width="*"/>
                 <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
-            
-            <Label Visibility="{Binding Path=MyProperty, Converter={StaticResource MyConverter}}" HorizontalContentAlignment="Right" Grid.Column="0">Пароль</Label>
-            <PasswordBox Grid.Column="1" x:Name="PasswordPasswordBox"/>
-            <TextBox Grid.Column="1" Visibility="Collapsed" x:Name="ShowPasswordTextBox"></TextBox>
-            <Button Grid.Column="2" x:Name="TogglePasswordButton" PreviewMouseDown="TogglePasswordButton_MouseDown" PreviewMouseUp="TogglePasswordButton_MouseUp">Показать пароль</Button>
+
+            <Label 
+                HorizontalContentAlignment="Right" 
+                Grid.Column="0"
+                VerticalAlignment="Center">Пароль</Label>
+            <TextBox
+                VerticalContentAlignment="Center"
+                Text="{Binding Password}" Grid.Column="1"></TextBox>
         </Grid>
         
-        <Button Margin="5" Grid.Row="3" x:Name="AuthorizeButton" Click="AuthorizeButton_Click">Вход</Button>
+        <Button 
+            Margin="5"
+            Grid.Row="3"
+            Command="{Binding LoginCmd}">Вход</Button>
     </Grid>
 </Page>

+ 0 - 37
src/DontHarmDesktop/Pages/AuthPage.xaml.cs

@@ -26,42 +26,5 @@ namespace DontHarmDesktop.Pages
             InitializeComponent();
             DataContext = new AuthViewModel();
         }
-
-        /// <summary>
-        /// Событие нажатия на кнопку "Показать пароль"
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void TogglePasswordButton_MouseDown(object sender, MouseButtonEventArgs e)
-        {
-            ShowPasswordTextBox.Text = PasswordPasswordBox.Password;
-            PasswordPasswordBox.Visibility = Visibility.Collapsed;
-            ShowPasswordTextBox.Visibility = Visibility.Visible;
-        }
-
-        /// <summary>
-        /// Событие отжатия кнопки "Показать пароль"
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void TogglePasswordButton_MouseUp(object sender, MouseButtonEventArgs e)
-        {
-            PasswordPasswordBox.Visibility = Visibility.Visible;
-            ShowPasswordTextBox.Visibility = Visibility.Collapsed;
-        }
-
-        /// <summary>
-        /// Событие клика на кнопку авторизации
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void AuthorizeButton_Click(object sender, RoutedEventArgs e)
-        {
-            var success = App.LogOn(LoginTextBox.Text, PasswordPasswordBox.Password);
-            if (success)
-            {
-                NavigationService.Navigate(new WelcomePage());
-            }
-        }
     }
 }

+ 0 - 14
src/DontHarmDesktop/Pages/FooterPage.xaml

@@ -1,14 +0,0 @@
-<Page x:Class="DontHarmDesktop.Pages.FooterPage"
-      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:DontHarmDesktop.Pages"
-      mc:Ignorable="d" 
-      d:DesignHeight="450" d:DesignWidth="800"
-      Title="FooterPage">
-
-    <StackPanel Orientation="Horizontal">
-        
-    </StackPanel>
-</Page>

+ 0 - 29
src/DontHarmDesktop/Pages/FooterPage.xaml.cs

@@ -1,29 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-using System.Windows.Threading;
-
-namespace DontHarmDesktop.Pages
-{
-    /// <summary>
-    /// Логика взаимодействия для FooterPage.xaml
-    /// </summary>
-    public partial class FooterPage : Page
-    {
-        public FooterPage()
-        {
-            InitializeComponent();
-        }
-    }
-}

+ 0 - 16
src/DontHarmDesktop/Pages/HeaderPage.xaml

@@ -1,16 +0,0 @@
-<Page x:Class="DontHarmDesktop.Pages.HeaderPage"
-      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-      xmlns:local="clr-namespace:DontHarmDesktop.Pages"
-      mc:Ignorable="d" 
-      d:DesignHeight="450" d:DesignWidth="800"
-      Title="HeaderPage"
-      Background="{StaticResource SecondaryColor}">
-
-    <StackPanel Orientation="Horizontal">
-        <Image Source="/Pages/Logo.png" Width="64" Height="64"></Image>
-        <TextBlock>Не навреди</TextBlock>
-    </StackPanel>
-</Page>

+ 0 - 28
src/DontHarmDesktop/Pages/HeaderPage.xaml.cs

@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace DontHarmDesktop.Pages
-{
-    /// <summary>
-    /// Логика взаимодействия для HeaderPage.xaml
-    /// </summary>
-    public partial class HeaderPage : Page
-    {
-        public HeaderPage()
-        {
-            InitializeComponent();
-        }
-    }
-}

+ 2 - 1
src/DontHarmDesktop/Pages/LogOnHistory.xaml.cs

@@ -33,8 +33,9 @@ namespace DontHarmDesktop.Pages
         {
             InitializeComponent();
             
+            var db = new Models.Entities();
             // Сбор записей и привязка callback-фильтра
-            var loginAttempts = App.Context.login_attempts.ToList();
+            var loginAttempts = db.login_attempts.ToList();
             loginAttemptsView = CollectionViewSource.GetDefaultView(loginAttempts);
             loginAttemptsView.Filter = FilterByLogin;
 

+ 5 - 3
src/DontHarmDesktop/Pages/UserInfoPage.xaml.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Data.Entity.Infrastructure;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -27,12 +28,13 @@ namespace DontHarmDesktop.Pages
             InitializeComponent();
             DataContext = this;
 
-            Models.users current_user = App.CurrentUser;
-
+            var db = new Models.Entities();
+                
+            Models.users current_user = AuthState.CurrentUser;
             UserImagePath = current_user.image_path;
 
             // Получение названия роли
-            Models.roles role = App.Context.roles.First(r => r.id == current_user.role);
+            Models.roles role = db.roles.First(r => r.id == current_user.role);
 
             GreetingTextBlock.Text = $"Добро пожаловать, {current_user.surname} {current_user.name} {current_user.patronymic} ({role.name})";
             //ProfilePictureImage.Source = new BitmapImage( new Uri("pack://application:,,,/Images/ProfilePictures/" + current_user.image_path, UriKind.Absolute));

+ 21 - 8
src/DontHarmDesktop/Pages/WelcomePage.xaml

@@ -21,14 +21,27 @@
                 <RowDefinition/>
                 <RowDefinition/>
             </Grid.RowDefinitions>
-            <Label Visibility="{Binding Path=AcceptWasteVisibility}" Name="AcceptWasteLabel" Grid.Row="0"><Hyperlink>Принять отходы</Hyperlink></Label>
-            <Label Visibility="{Binding Path=MakeReportsVisibility}" Name="MakeReportsLabel" Grid.Row="1"><Hyperlink>Сформировать отчёты</Hyperlink></Label>
-            <Label Visibility="{Binding Path=UtilizerVisibility}" Name="UtilizerLabel" Grid.Row="2"><Hyperlink>Работа с утилизатором</Hyperlink></Label>
-            <Label Visibility="{Binding Path=ViewReportsVisibility}" Name="ViewReportsLabel" Grid.Row="3"><Hyperlink>Просмотреть отчёты</Hyperlink></Label>
-            <Label Visibility="{Binding Path=MakeInvoiceVisibility}" Name="MakeInvoiceLabel" Grid.Row="4"><Hyperlink>Сформировать счёт предприятию</Hyperlink></Label>
-            <Label Visibility="{Binding Path=LoginHistoryVisibility}" Name="LoginHistoryLabel" Grid.Row="5"><Hyperlink NavigateUri="/Pages/LogOnHistory.xaml">Просмотр истории входа</Hyperlink></Label>
-            <Label Visibility="{Binding Path=UsageVisibility}" Name="UsageLabel" Grid.Row="6"><Hyperlink>Просмотр расходов</Hyperlink></Label>
+            <Label Visibility="{Binding Path=AcceptWasteVisibility}" Name="AcceptWasteLabel" Grid.Row="0">
+                <Hyperlink>Принять отходы</Hyperlink>
+            </Label>
+            <Label Visibility="{Binding Path=MakeReportsVisibility}" Name="MakeReportsLabel" Grid.Row="1">
+                <Hyperlink>Сформировать отчёты</Hyperlink>
+            </Label>
+            <Label Visibility="{Binding Path=UtilizerVisibility}" Name="UtilizerLabel" Grid.Row="2">
+                <Hyperlink>Работа с утилизатором</Hyperlink>
+            </Label>
+            <Label Visibility="{Binding Path=ViewReportsVisibility}" Name="ViewReportsLabel" Grid.Row="3">
+                <Hyperlink>Просмотреть отчёты</Hyperlink>
+            </Label>
+            <Label Visibility="{Binding Path=MakeInvoiceVisibility}" Name="MakeInvoiceLabel" Grid.Row="4">
+                <Hyperlink>Сформировать счёт предприятию</Hyperlink>
+            </Label>
+            <Label Visibility="{Binding Path=LoginHistoryVisibility}" Name="LoginHistoryLabel" Grid.Row="5">
+                <Hyperlink NavigateUri="/Pages/LogOnHistory.xaml">Просмотр истории входа</Hyperlink>
+            </Label>
+            <Label Visibility="{Binding Path=UsageVisibility}" Name="UsageLabel" Grid.Row="6">
+                <Hyperlink>Просмотр расходов</Hyperlink>
+            </Label>
         </Grid>
-        <Button Click="LogoffButton_Click" Name="LogoffButton">Выход</Button>
     </StackPanel>
 </Page>

+ 7 - 18
src/DontHarmDesktop/Pages/WelcomePage.xaml.cs

@@ -33,7 +33,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 3)
+                if (AuthState.CurrentUser.role == 3)
                 {
                     return Visibility.Visible;
                 } else { 
@@ -49,7 +49,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 4)
+                if (AuthState.CurrentUser.role == 4)
                 {
                     return Visibility.Visible;
                 }
@@ -68,7 +68,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 3 || App.CurrentUser.role == 1)
+                if (AuthState.CurrentUser.role == 3 || AuthState.CurrentUser.role == 1)
                 {
                     return Visibility.Visible;
                 }
@@ -86,7 +86,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 2)
+                if (AuthState.CurrentUser.role == 2)
                 {
                     return Visibility.Visible;
                 }
@@ -104,7 +104,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 2)
+                if (AuthState.CurrentUser.role == 2)
                 {
                     return Visibility.Visible;
                 }
@@ -122,7 +122,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 1)
+                if (AuthState.CurrentUser.role == 1)
                 {
                     return Visibility.Visible;
                 }
@@ -140,7 +140,7 @@ namespace DontHarmDesktop.Pages
         {
             get
             {
-                if (App.CurrentUser.role == 1)
+                if (AuthState.CurrentUser.role == 1)
                 {
                     return Visibility.Visible;
                 }
@@ -150,16 +150,5 @@ namespace DontHarmDesktop.Pages
                 }
             }
         }
-
-        /// <summary>
-        /// Клик кнопки выхода
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void LogoffButton_Click(object sender, RoutedEventArgs e)
-        {
-            App.LogOff();
-            NavigationService.Navigate(new AuthPage());
-        }
     }
 }

+ 77 - 6
src/DontHarmDesktop/ViewModels/AuthViewModel.cs

@@ -3,20 +3,91 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Prism.Mvvm;
+using Prism.Commands;
+using System.Windows;
+using System.Runtime.Remoting.Contexts;
+using DontHarmDesktop.Models;
 
 namespace DontHarmDesktop.ViewModels
 {
-    class AuthViewModel
+    class AuthViewModel : BindableBase
     {
-        public bool MyProperty {
-            get {
-                return true;
+        public DelegateCommand LoginCmd { get; set; }
+        public string Login { get; set; }
+        public string Password { get; set; }
+
+        public AuthViewModel() 
+        {
+            LoginCmd = new DelegateCommand(LoginExec);
+        }
+
+        private void LoginExec()
+        {
+            if (AuthState.LoginBlocked)
+            {
+                if (DateTime.Now < AuthState.LoginBlockedUntil)
+                {
+                    // Вход всё ещё заблокирован
+                    MessageBox.Show("Вход заблокирован до " + AuthState.LoginBlockedUntil);
+                    AddLoginRecord(false, Login);
+                    return;
+                }
+
+                // Вход уже не заблокирован, можем идти дальше
+                AuthState.LoginBlocked = false;
+                AuthState.LoginBlockedUntil = null;
             }
-            
-            set
+
+            // Пытаемся войти в приложение
+            var db = new Models.Entities();
+            var result = db.users.FirstOrDefault(p => p.login == Login && p.password == Password);
+            if (result == null)
             {
+                MessageBox.Show("Логин или пароль не совпадают");
+                AuthState.AttemptsFailed++;
+                if (AuthState.AttemptsFailed > 1)
+                {
+                    // Количество попыток превышено, блокировка входа на 10 секунд
+                    AuthState.BlockLogin(TimeSpan.FromSeconds(10));
+                }
+                AddLoginRecord(false, Login);
+                return;
+            }
 
+            // Вход успешен, устанавливаем параметры AuthState
+            AuthState.AttemptsFailed    = 0;
+            AuthState.CurrentUser       = result;
+            AuthState.SessionStart      = DateTime.Now;
+            AuthState.SessionIsLimited  = false;
+
+            // Если роль пользователя - лаборант, надо запустить таймер кварцевания
+            // на 2 минуты
+            if (result.role == 2)
+            {
+                AuthState.SessionEnd = AuthState.SessionStart + TimeSpan.FromMinutes(2);
+                AuthState.SessionIsLimited = true;
+                (App.Current.MainWindow as MainWindow).StartLogoffTimer();
             }
+
+            (App.Current.MainWindow as MainWindow).MainFrame.Navigate(new Pages.WelcomePage());
+            AddLoginRecord(true, Login);
+        }
+
+        /// <summary>
+        /// Добавляет в БД попытку входа в приложение
+        /// </summary>
+        private void AddLoginRecord(bool successful, string login)
+        {
+            var record = new login_attempts();
+            record.successfull = Convert.ToByte(successful);
+            record.login = login;
+            record.ip_address = "127.0.0.1";
+            record.created_at = DateTime.Now;
+
+            var db = new Models.Entities();
+            db.login_attempts.Add(record);
+            db.SaveChanges();
         }
     }
 }

+ 30 - 0
src/DontHarmDesktop/ViewModels/MainViewModel.cs

@@ -0,0 +1,30 @@
+using DontHarmDesktop.Pages;
+using Prism.Commands;
+using Prism.Mvvm;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DontHarmDesktop.ViewModels
+{
+    public class MainViewModel : BindableBase
+    {
+        public DelegateCommand LogoutCmd { get; set; }
+
+        public MainViewModel() 
+        {
+            LogoutCmd = new DelegateCommand(LogoutExecuted);
+        }
+
+        private void LogoutExecuted()
+        {
+            AuthState.CurrentUser = null;
+            var mainWindow = (App.Current.MainWindow as MainWindow);
+            mainWindow.LogoutTimer.Stop();
+            mainWindow.MainFrame.Navigate(new AuthPage());
+            mainWindow.TimerTextBlock.Text = String.Empty;
+        }
+    }
+}

+ 7 - 0
src/DontHarmDesktop/packages.config

@@ -2,4 +2,11 @@
 <packages>
   <package id="EntityFramework" version="6.2.0" targetFramework="net472" />
   <package id="EntityFramework.ru" version="6.2.0" targetFramework="net472" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net472" />
+  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="8.0.1" targetFramework="net472" />
+  <package id="Prism.Container.Abstractions" version="9.0.106" targetFramework="net472" />
+  <package id="Prism.Core" version="9.0.537" targetFramework="net472" />
+  <package id="Prism.Events" version="9.0.537" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
 </packages>

+ 1 - 1
tools/users_import/users.py

@@ -19,7 +19,7 @@ for index, row in enumerate(data):
     password = parts[3]
     usertype = parts[7]
 
-    script += f"INSERT INTO users (type,login,password,surname,name) VALUES ({usertype}, '{login}', '{password}', '{surname}', '{name}')\n"
+    script += f"INSERT INTO users (role,login,password,surname,name) VALUES ({usertype}, '{login}', '{password}', '{surname}', '{name}')\n"
     script += "SELECT @inserted_row_id = SCOPE_IDENTITY();\n"
 
     services = json.loads(parts[6][1:-1])