Initial commit: IT Nexus Web-App
This commit is contained in:
28
agent-cs/UI/AdminLoginDialog.xaml
Normal file
28
agent-cs/UI/AdminLoginDialog.xaml
Normal file
@@ -0,0 +1,28 @@
|
||||
<Window x:Class="ITNexusAgent.UI.AdminLoginDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="IT Nexus – Admin-Login"
|
||||
Width="360" Height="230"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize" Background="White" FontFamily="Segoe UI">
|
||||
<StackPanel Margin="24">
|
||||
<TextBlock Text="Admin-Anmeldung" FontSize="14" FontWeight="Bold"
|
||||
Foreground="#1F2937" Margin="0,0,0,16"/>
|
||||
<TextBlock Text="Benutzername" FontSize="11" Foreground="#6B7280" Margin="0,0,0,4"/>
|
||||
<TextBox x:Name="UsernameBox" Height="32" Padding="8,4" FontSize="12"
|
||||
BorderBrush="#D1D5DB" Margin="0,0,0,10"/>
|
||||
<TextBlock Text="Passwort" FontSize="11" Foreground="#6B7280" Margin="0,0,0,4"/>
|
||||
<PasswordBox x:Name="PasswordBox" Height="32" Padding="8,4" FontSize="12"
|
||||
BorderBrush="#D1D5DB" Margin="0,0,0,4"
|
||||
KeyDown="PasswordBox_KeyDown"/>
|
||||
<TextBlock x:Name="ErrorLabel" FontSize="11" Foreground="#EF4444"
|
||||
Margin="0,4,0,0" Visibility="Collapsed"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,0,0">
|
||||
<Button Content="Abbrechen" Width="80" Height="32" Margin="0,0,8,0"
|
||||
Click="Cancel_Click" Background="#F9FAFB" BorderBrush="#D1D5DB" FontSize="12"/>
|
||||
<Button x:Name="LoginButton" Content="Anmelden" Width="90" Height="32"
|
||||
Click="Login_Click" Background="#0078D4" Foreground="White"
|
||||
BorderThickness="0" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
65
agent-cs/UI/AdminLoginDialog.xaml.cs
Normal file
65
agent-cs/UI/AdminLoginDialog.xaml.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace ITNexusAgent.UI;
|
||||
|
||||
public partial class AdminLoginDialog : Window
|
||||
{
|
||||
public string Token { get; private set; } = "";
|
||||
public string Username { get; private set; } = "";
|
||||
private readonly string _serverUrl;
|
||||
|
||||
public AdminLoginDialog(string serverUrl)
|
||||
{
|
||||
InitializeComponent();
|
||||
_serverUrl = serverUrl.TrimEnd('/');
|
||||
}
|
||||
|
||||
private async void Login_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var username = UsernameBox.Text.Trim();
|
||||
var password = PasswordBox.Password;
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return;
|
||||
|
||||
LoginButton.IsEnabled = false;
|
||||
ErrorLabel.Visibility = Visibility.Collapsed;
|
||||
|
||||
try
|
||||
{
|
||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
||||
var body = JsonConvert.SerializeObject(new { username, password });
|
||||
var resp = await http.PostAsync($"{_serverUrl}/api/auth/login",
|
||||
new StringContent(body, Encoding.UTF8, "application/json"));
|
||||
var json = await resp.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeAnonymousType(json, new { token = "" });
|
||||
|
||||
if (resp.IsSuccessStatusCode && !string.IsNullOrEmpty(result?.token))
|
||||
{
|
||||
Token = result.token;
|
||||
Username = username;
|
||||
DialogResult = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorLabel.Text = "Ungültige Anmeldedaten";
|
||||
ErrorLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ErrorLabel.Text = "Verbindung fehlgeschlagen";
|
||||
ErrorLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
finally { LoginButton.IsEnabled = true; }
|
||||
}
|
||||
|
||||
private void PasswordBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter) Login_Click(sender, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e) => DialogResult = false;
|
||||
}
|
||||
510
agent-cs/UI/DashboardWindow.xaml
Normal file
510
agent-cs/UI/DashboardWindow.xaml
Normal file
@@ -0,0 +1,510 @@
|
||||
<Window x:Class="ITNexusAgent.UI.DashboardWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="IT Nexus Agent"
|
||||
Icon="pack://application:,,,/icon.ico"
|
||||
Width="560" Height="640"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize"
|
||||
StateChanged="Window_StateChanged"
|
||||
Background="Transparent"
|
||||
FontFamily="Segoe UI"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
MinWidth="480" MinHeight="540">
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="Card" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1C1C1F"/>
|
||||
<Setter Property="CornerRadius" Value="12"/>
|
||||
<Setter Property="Padding" Value="16,14"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BtnAction" TargetType="Button">
|
||||
<Setter Property="Background" Value="#2A2A2E"/>
|
||||
<Setter Property="Foreground" Value="#E4E4E7"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="9"
|
||||
BorderThickness="1" BorderBrush="#333337">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#35353A"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="#4A4A50"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1A1A1D"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BtnPrimary" TargetType="Button" BasedOn="{StaticResource BtnAction}">
|
||||
<Setter Property="Background" Value="#1D4ED8"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="9" BorderThickness="0">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#2563EB"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1E40AF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BtnWarn" TargetType="Button" BasedOn="{StaticResource BtnAction}">
|
||||
<Setter Property="Background" Value="#27180A"/>
|
||||
<Setter Property="Foreground" Value="#F97316"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="9"
|
||||
BorderThickness="1" BorderBrush="#3D2510">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#352010"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BtnGhost" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="#52525B"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<TextBlock x:Name="T" Text="{TemplateBinding Content}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
HorizontalAlignment="Center"/>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="T" Property="Foreground" Value="#71717A"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BtnAdminAction" TargetType="Button" BasedOn="{StaticResource BtnAction}">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<!-- Outer container -->
|
||||
<Grid>
|
||||
<Border x:Name="OuterBorder" Background="#111113" CornerRadius="14" BorderBrush="#2A2A2E" BorderThickness="1"
|
||||
Margin="16">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black" Opacity="0.4" BlurRadius="20" ShadowDepth="0"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ═══ TITLEBAR (draggable) ═══ -->
|
||||
<Border x:Name="TitleBarBorder" Grid.Row="0" Background="#18181B" CornerRadius="14,14,0,0"
|
||||
MouseLeftButtonDown="TitleBar_MouseDown">
|
||||
<Grid Margin="18,14,18,14">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Text="IT Nexus Agent" FontSize="15" FontWeight="Bold"
|
||||
Foreground="#FAFAFA"/>
|
||||
<TextBlock x:Name="HostnameLabel" FontSize="11" Foreground="#52525B"
|
||||
Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<!-- Status -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,20,0" VerticalAlignment="Center">
|
||||
<Ellipse x:Name="StatusDot" Width="8" Height="8" Margin="0,0,7,0" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="StatusLabel" FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<!-- Minimieren -->
|
||||
<Button Width="32" Height="32" Click="Minimize_Click" ToolTip="Minimieren">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="6">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="9" Foreground="#52525B"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#2A2A2E"/>
|
||||
<Setter Property="Foreground" Value="#A1A1AA"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- Maximieren -->
|
||||
<Button Width="32" Height="32" Click="Maximize_Click" ToolTip="Maximieren">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="6">
|
||||
<TextBlock x:Name="MaxIcon" Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="9" Foreground="#52525B"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#2A2A2E"/>
|
||||
<Setter TargetName="MaxIcon" Property="Foreground" Value="#A1A1AA"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<!-- Schließen -->
|
||||
<Button Width="32" Height="32" Click="Close_Click" ToolTip="Schließen">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="6">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="9" Foreground="#52525B"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#C0392B"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ CONTENT ═══ -->
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden"
|
||||
Background="#111113">
|
||||
<StackPanel Margin="14,12,14,4">
|
||||
|
||||
<!-- Metric Cards -->
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Background="#1C1C1F" CornerRadius="12" Padding="14,12">
|
||||
<StackPanel>
|
||||
<TextBlock Text="CPU" FontSize="10" Foreground="#52525B"
|
||||
FontWeight="SemiBold" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="CpuLabel" FontSize="24" FontWeight="Bold"
|
||||
Foreground="#FAFAFA" Margin="0,0,0,8"/>
|
||||
<ProgressBar x:Name="CpuBar" Height="3" BorderThickness="0"
|
||||
Background="#2A2A2E" Minimum="0" Maximum="100"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="2" Background="#1C1C1F" CornerRadius="12" Padding="14,12">
|
||||
<StackPanel>
|
||||
<TextBlock Text="RAM" FontSize="10" Foreground="#52525B"
|
||||
FontWeight="SemiBold" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="RamLabel" FontSize="14" FontWeight="Bold"
|
||||
Foreground="#FAFAFA" Margin="0,0,0,8" TextWrapping="Wrap"/>
|
||||
<ProgressBar x:Name="RamBar" Height="3" BorderThickness="0"
|
||||
Background="#2A2A2E" Minimum="0" Maximum="100"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="4" Background="#1C1C1F" CornerRadius="12" Padding="14,12">
|
||||
<StackPanel>
|
||||
<TextBlock Text="DISK" FontSize="10" Foreground="#52525B"
|
||||
FontWeight="SemiBold" Margin="0,0,0,6"/>
|
||||
<TextBlock x:Name="DiskLabel" FontSize="14" FontWeight="Bold"
|
||||
Foreground="#FAFAFA" Margin="0,0,0,8" TextWrapping="Wrap"/>
|
||||
<ProgressBar x:Name="DiskBar" Height="3" BorderThickness="0"
|
||||
Background="#2A2A2E" Minimum="0" Maximum="100"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Status Card -->
|
||||
<Border Background="#1C1C1F" CornerRadius="12" Padding="16,14" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13" Foreground="#3F3F46" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="BitLocker" FontSize="12"
|
||||
Foreground="#A1A1AA" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="BitlockerLabel" Grid.Column="2" FontSize="12"
|
||||
FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle Height="1" Fill="#27272A" Margin="0,0,0,10"/>
|
||||
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13" Foreground="#3F3F46" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="Microsoft Defender" FontSize="12"
|
||||
Foreground="#A1A1AA" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="DefenderLabel" Grid.Column="2" FontSize="12"
|
||||
FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle Height="1" Fill="#27272A" Margin="0,0,0,10"/>
|
||||
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13" Foreground="#3F3F46" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="Windows Updates" FontSize="12"
|
||||
Foreground="#A1A1AA" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="UpdatesLabel" Grid.Column="2" FontSize="12"
|
||||
FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
|
||||
<Rectangle Height="1" Fill="#27272A" Margin="0,0,0,10"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13" Foreground="#3F3F46" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="1" Text="Seriennummer" FontSize="12"
|
||||
Foreground="#A1A1AA" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="SerialLabel" Grid.Column="2" FontSize="11"
|
||||
Foreground="#52525B" VerticalAlignment="Center"
|
||||
FontFamily="Consolas"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Installierte Software -->
|
||||
<Border Background="#1C1C1F" CornerRadius="12" Padding="16,14" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="INSTALLIERTE SOFTWARE" FontSize="10" FontWeight="SemiBold"
|
||||
Foreground="#52525B" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="SoftwareCountLabel" Grid.Column="1" FontSize="10"
|
||||
Foreground="#3F3F46" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<Border Background="#27272A" CornerRadius="8" BorderBrush="#3F3F46"
|
||||
BorderThickness="1" Padding="10,0" Margin="0,0,0,8">
|
||||
<TextBox x:Name="SoftwareSearch" Background="Transparent"
|
||||
Foreground="#71717A" BorderThickness="0" FontSize="12"
|
||||
Height="32" VerticalContentAlignment="Center"
|
||||
CaretBrush="White"
|
||||
TextChanged="SoftwareSearch_TextChanged"/>
|
||||
</Border>
|
||||
<ListBox x:Name="SoftwareList" MaxHeight="200" Background="Transparent"
|
||||
BorderThickness="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="Foreground" Value="#A1A1AA"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Padding" Value="4,3"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}"
|
||||
CornerRadius="4" Padding="6,3">
|
||||
<TextBlock Text="{TemplateBinding Content}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
FontSize="11" TextTrimming="CharacterEllipsis"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#27272A"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1D4ED820"/>
|
||||
<Setter Property="Foreground" Value="#FAFAFA"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Letzter Check-in -->
|
||||
<TextBlock x:Name="LastCheckinLabel" FontSize="10" Foreground="#3F3F46"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<Button x:Name="WebButton" Height="40" Margin="0,0,0,8"
|
||||
Content="IT Nexus Web öffnen"
|
||||
Click="WebButton_Click" Style="{StaticResource BtnPrimary}"
|
||||
FontSize="12" FontWeight="SemiBold"/>
|
||||
|
||||
<Grid Margin="0,0,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Height="38"
|
||||
Content="Nachricht senden"
|
||||
Click="MessageButton_Click"
|
||||
Style="{StaticResource BtnAction}"
|
||||
FontSize="11"/>
|
||||
<Button Grid.Column="2" Height="38"
|
||||
Content="Neustart anfordern"
|
||||
Click="RebootButton_Click"
|
||||
Style="{StaticResource BtnWarn}"
|
||||
FontSize="11"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Admin -->
|
||||
<Border Background="#1C1C1F" CornerRadius="12" Padding="14,12" Margin="0,0,0,4">
|
||||
<StackPanel>
|
||||
<Button x:Name="AdminLoginButton" Content="Admin-Modus"
|
||||
Height="26" Click="AdminLoginButton_Click"
|
||||
Style="{StaticResource BtnGhost}"/>
|
||||
<StackPanel x:Name="AdminPanel" Visibility="Collapsed" Margin="0,12,0,0">
|
||||
<TextBlock x:Name="AdminUserLabel" FontSize="11" Foreground="#3B82F6"
|
||||
FontWeight="SemiBold" Margin="0,0,0,10"
|
||||
HorizontalAlignment="Center"/>
|
||||
<UniformGrid Columns="2" Rows="2">
|
||||
<Button Content="Sofort Check-in" Height="34" Margin="0,0,4,4"
|
||||
Click="AdminCheckin_Click" Style="{StaticResource BtnAdminAction}"/>
|
||||
<Button Content="Updates prüfen" Height="34" Margin="4,0,0,4"
|
||||
Click="AdminUpdates_Click" Style="{StaticResource BtnAdminAction}"/>
|
||||
<Button Content="Agent-Log" Height="34" Margin="0,0,4,0"
|
||||
Click="AdminLog_Click" Style="{StaticResource BtnAdminAction}"/>
|
||||
<Button Content="Neu starten" Height="34" Margin="4,0,0,0"
|
||||
Click="AdminRestart_Click" Style="{StaticResource BtnAdminAction}"/>
|
||||
</UniformGrid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ═══ FOOTER ═══ -->
|
||||
<Border Grid.Row="2" Background="#18181B" CornerRadius="0,0,14,14"
|
||||
Padding="0,10">
|
||||
<TextBlock x:Name="VersionLabel" FontSize="10" Foreground="#3F3F46"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
|
||||
<!-- ═══ ADMIN LOGIN MODAL OVERLAY ═══ -->
|
||||
<Border x:Name="AdminOverlay" Visibility="Collapsed"
|
||||
Background="#80000000" CornerRadius="14">
|
||||
<Border Background="#1C1C1F" CornerRadius="12" Padding="28,24"
|
||||
Width="320" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black" Opacity="0.6" BlurRadius="30" ShadowDepth="0"/>
|
||||
</Border.Effect>
|
||||
<StackPanel>
|
||||
<TextBlock Text="Admin-Anmeldung" FontSize="15" FontWeight="Bold"
|
||||
Foreground="#FAFAFA" Margin="0,0,0,4"/>
|
||||
<TextBlock Text="IT Nexus Zugangsdaten eingeben"
|
||||
FontSize="11" Foreground="#52525B" Margin="0,0,0,20"/>
|
||||
|
||||
<TextBlock Text="Benutzername" FontSize="11" Foreground="#71717A" Margin="0,0,0,6"/>
|
||||
<Border Background="#27272A" CornerRadius="8" BorderBrush="#3F3F46"
|
||||
BorderThickness="1" Padding="12,0" Margin="0,0,0,12">
|
||||
<TextBox x:Name="AdminUsernameBox" Background="Transparent"
|
||||
Foreground="#FAFAFA" BorderThickness="0" FontSize="13"
|
||||
Height="36" VerticalContentAlignment="Center"
|
||||
CaretBrush="White"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="Passwort" FontSize="11" Foreground="#71717A" Margin="0,0,0,6"/>
|
||||
<Border Background="#27272A" CornerRadius="8" BorderBrush="#3F3F46"
|
||||
BorderThickness="1" Padding="12,0" Margin="0,0,0,6">
|
||||
<PasswordBox x:Name="AdminPasswordBox" Background="Transparent"
|
||||
Foreground="#FAFAFA" BorderThickness="0" FontSize="13"
|
||||
Height="36" VerticalContentAlignment="Center"
|
||||
KeyDown="AdminPasswordBox_KeyDown"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock x:Name="AdminErrorLabel" FontSize="11" Foreground="#EF4444"
|
||||
Margin="0,4,0,0" Visibility="Collapsed"/>
|
||||
|
||||
<Grid Margin="0,20,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="Abbrechen" Height="38"
|
||||
Click="AdminCancelModal_Click"
|
||||
Style="{StaticResource BtnAction}" FontSize="12"/>
|
||||
<Button x:Name="AdminLoginBtn" Grid.Column="2" Content="Anmelden" Height="38"
|
||||
Click="AdminLoginModal_Click"
|
||||
Style="{StaticResource BtnPrimary}" FontSize="12" FontWeight="SemiBold"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
503
agent-cs/UI/DashboardWindow.xaml.cs
Normal file
503
agent-cs/UI/DashboardWindow.xaml.cs
Normal file
@@ -0,0 +1,503 @@
|
||||
using ITNexusAgent.Models;
|
||||
using ITNexusAgent.Services;
|
||||
using Newtonsoft.Json;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using WinForms = System.Windows.Forms;
|
||||
using DrawingIcon = System.Drawing.Icon;
|
||||
using DrawingSystemIcons = System.Drawing.SystemIcons;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
|
||||
using MediaColor = System.Windows.Media.Color;
|
||||
using ProgressBar = System.Windows.Controls.ProgressBar;
|
||||
|
||||
namespace ITNexusAgent.UI;
|
||||
|
||||
public partial class DashboardWindow : Window
|
||||
{
|
||||
private const string StatusPath = @"C:\ProgramData\IT Nexus Agent\status.json";
|
||||
private const string LogPath = @"C:\ProgramData\IT Nexus Agent\agent.log";
|
||||
private readonly DispatcherTimer _timer;
|
||||
private AgentConfig? _config;
|
||||
private string? _adminToken;
|
||||
private WinForms.NotifyIcon? _trayIcon;
|
||||
private List<string> _allSoftware = [];
|
||||
private bool _softwarePlaceholderActive = true;
|
||||
|
||||
public DashboardWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
try { _config = AgentConfig.Load(@"C:\ProgramData\IT Nexus Agent\config.json"); }
|
||||
catch { }
|
||||
|
||||
InitTrayIcon();
|
||||
InitSoftwareSearch();
|
||||
|
||||
VersionLabel.Text = $"IT Nexus Agent v2.0.0 · Cereda Systems GmbH";
|
||||
|
||||
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
|
||||
_timer.Tick += (_, _) => RefreshStatus();
|
||||
_timer.Start();
|
||||
|
||||
RefreshStatus();
|
||||
}
|
||||
|
||||
private void InitSoftwareSearch()
|
||||
{
|
||||
SoftwareSearch.Text = "Software suchen...";
|
||||
SoftwareSearch.Foreground = new SolidColorBrush(MediaColor.FromRgb(82, 82, 91));
|
||||
|
||||
SoftwareSearch.GotFocus += (_, _) =>
|
||||
{
|
||||
if (_softwarePlaceholderActive)
|
||||
{
|
||||
_softwarePlaceholderActive = false;
|
||||
SoftwareSearch.Text = "";
|
||||
SoftwareSearch.Foreground = new SolidColorBrush(MediaColor.FromRgb(250, 250, 250));
|
||||
}
|
||||
};
|
||||
|
||||
SoftwareSearch.LostFocus += (_, _) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SoftwareSearch.Text))
|
||||
{
|
||||
_softwarePlaceholderActive = true;
|
||||
SoftwareSearch.Text = "Software suchen...";
|
||||
SoftwareSearch.Foreground = new SolidColorBrush(MediaColor.FromRgb(82, 82, 91));
|
||||
ApplySoftwareFilter("");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void RefreshStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(StatusPath))
|
||||
{
|
||||
SetOffline("Noch kein Check-in");
|
||||
return;
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(StatusPath);
|
||||
var status = JsonConvert.DeserializeObject<StatusCache>(json);
|
||||
if (status == null) return;
|
||||
|
||||
HostnameLabel.Text = $"{status.Hostname} · {status.LastUser}";
|
||||
|
||||
var ago = DateTime.Now - status.LastCheckin;
|
||||
var agoText = ago.TotalSeconds < 60
|
||||
? $"vor {(int)ago.TotalSeconds} Sekunden"
|
||||
: ago.TotalMinutes < 60
|
||||
? $"vor {(int)ago.TotalMinutes} Minuten"
|
||||
: $"vor {(int)ago.TotalHours} Stunden";
|
||||
|
||||
if (status.Online && ago.TotalMinutes < 3)
|
||||
{
|
||||
StatusDot.Fill = new SolidColorBrush(MediaColor.FromRgb(34, 197, 94));
|
||||
StatusLabel.Text = "Online";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusDot.Fill = new SolidColorBrush(MediaColor.FromRgb(239, 68, 68));
|
||||
StatusLabel.Text = "Offline";
|
||||
}
|
||||
|
||||
// Metrics
|
||||
var cpu = status.CpuUsage ?? 0;
|
||||
CpuLabel.Text = $"{cpu:0}%";
|
||||
CpuBar.Value = cpu;
|
||||
SetBarColor(CpuBar, cpu);
|
||||
|
||||
if (status.RamTotal > 0)
|
||||
{
|
||||
var ramPct = (status.RamUsed ?? 0) / status.RamTotal.Value * 100;
|
||||
RamLabel.Text = $"{status.RamUsed:0.0}/{status.RamTotal:0} GB";
|
||||
RamBar.Value = ramPct;
|
||||
SetBarColor(RamBar, ramPct);
|
||||
}
|
||||
|
||||
if (status.DiskTotal > 0)
|
||||
{
|
||||
var diskUsed = status.DiskTotal.Value - (status.DiskFree ?? 0);
|
||||
var diskPct = diskUsed / status.DiskTotal.Value * 100;
|
||||
DiskLabel.Text = $"{diskUsed:0}/{status.DiskTotal:0} GB";
|
||||
DiskBar.Value = diskPct;
|
||||
SetBarColor(DiskBar, diskPct);
|
||||
}
|
||||
|
||||
// Security
|
||||
BitlockerLabel.Text = status.BitlockerStatus switch
|
||||
{
|
||||
"encrypted" => "✓ Verschlüsselt",
|
||||
"off" => "✗ Nicht aktiv",
|
||||
_ => "? Unbekannt"
|
||||
};
|
||||
BitlockerLabel.Foreground = status.BitlockerStatus == "encrypted"
|
||||
? new SolidColorBrush(MediaColor.FromRgb(22, 163, 74))
|
||||
: new SolidColorBrush(MediaColor.FromRgb(239, 68, 68));
|
||||
|
||||
DefenderLabel.Text = status.DefenderEnabled
|
||||
? status.DefenderSignaturesAge <= 3
|
||||
? "✓ Aktiv · Signaturen aktuell"
|
||||
: $"⚠ Aktiv · Signaturen {status.DefenderSignaturesAge}d alt"
|
||||
: "✗ Inaktiv";
|
||||
DefenderLabel.Foreground = status.DefenderEnabled
|
||||
? new SolidColorBrush(MediaColor.FromRgb(22, 163, 74))
|
||||
: new SolidColorBrush(MediaColor.FromRgb(239, 68, 68));
|
||||
|
||||
UpdatesLabel.Text = status.PendingUpdates == 0 ? "✓ Aktuell" : $"⚠ {status.PendingUpdates} ausstehend";
|
||||
UpdatesLabel.Foreground = status.PendingUpdates == 0
|
||||
? new SolidColorBrush(MediaColor.FromRgb(22, 163, 74))
|
||||
: new SolidColorBrush(MediaColor.FromRgb(245, 158, 11));
|
||||
|
||||
SerialLabel.Text = string.IsNullOrEmpty(status.HardwareSerial) ? "—" : status.HardwareSerial;
|
||||
LastCheckinLabel.Text = $"Letzter Check-in: {agoText}";
|
||||
|
||||
// Software-Liste nur aktualisieren wenn sich die Anzahl geändert hat
|
||||
if (status.InstalledSoftware.Count != _allSoftware.Count ||
|
||||
(status.InstalledSoftware.Count > 0 && _allSoftware.Count == 0))
|
||||
{
|
||||
_allSoftware = status.InstalledSoftware;
|
||||
var filterText = _softwarePlaceholderActive ? "" : SoftwareSearch.Text;
|
||||
ApplySoftwareFilter(filterText);
|
||||
}
|
||||
}
|
||||
catch { SetOffline("Fehler beim Laden"); }
|
||||
}
|
||||
|
||||
private void ApplySoftwareFilter(string filter)
|
||||
{
|
||||
var filtered = string.IsNullOrWhiteSpace(filter)
|
||||
? _allSoftware
|
||||
: _allSoftware.Where(s => s.Contains(filter, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
SoftwareList.ItemsSource = filtered;
|
||||
SoftwareCountLabel.Text = _allSoftware.Count > 0
|
||||
? $"{filtered.Count} / {_allSoftware.Count}"
|
||||
: "";
|
||||
}
|
||||
|
||||
private void SoftwareSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
if (_softwarePlaceholderActive) return;
|
||||
ApplySoftwareFilter(SoftwareSearch.Text);
|
||||
}
|
||||
|
||||
private void SetOffline(string reason)
|
||||
{
|
||||
StatusDot.Fill = new SolidColorBrush(MediaColor.FromRgb(156, 163, 175));
|
||||
StatusLabel.Text = reason;
|
||||
}
|
||||
|
||||
private static void SetBarColor(System.Windows.Controls.ProgressBar bar, double pct)
|
||||
{
|
||||
var color = pct > 90 ? MediaColor.FromRgb(239, 68, 68)
|
||||
: pct > 70 ? MediaColor.FromRgb(245, 158, 11)
|
||||
: MediaColor.FromRgb(59, 130, 246);
|
||||
bar.Foreground = new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
private void WebButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var url = _config?.ServerUrl ?? "https://it-nexus.cereda-systems.de";
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private async void MessageButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new MessageDialog();
|
||||
if (dlg.ShowDialog() != true || string.IsNullOrWhiteSpace(dlg.Message)) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config != null)
|
||||
{
|
||||
var api = new ApiService(_config.ServerUrl, _config.AgentKey);
|
||||
await api.SendMessageAsync(Environment.MachineName, dlg.Message);
|
||||
MessageBox.Show("Nachricht wurde gesendet.", "IT Nexus", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show("Fehler beim Senden.", "IT Nexus", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async void RebootButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"Möchtest du einen Neustart bei der IT anfordern?\nDein Rechner wird nicht sofort neu gestartet.",
|
||||
"Neustart anfordern", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config != null)
|
||||
{
|
||||
var api = new ApiService(_config.ServerUrl, _config.AgentKey);
|
||||
await api.SendRebootRequestAsync(Environment.MachineName);
|
||||
MessageBox.Show("Neustart wurde angefordert. Die IT wird sich darum kümmern.",
|
||||
"IT Nexus", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void AdminLoginButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_adminToken != null)
|
||||
{
|
||||
_adminToken = null;
|
||||
AdminPanel.Visibility = Visibility.Collapsed;
|
||||
AdminLoginButton.Content = "Admin-Modus";
|
||||
return;
|
||||
}
|
||||
AdminUsernameBox.Text = "";
|
||||
AdminPasswordBox.Password = "";
|
||||
AdminErrorLabel.Visibility = Visibility.Collapsed;
|
||||
AdminOverlay.Visibility = Visibility.Visible;
|
||||
AdminUsernameBox.Focus();
|
||||
}
|
||||
|
||||
private void AdminCancelModal_Click(object sender, RoutedEventArgs e)
|
||||
=> AdminOverlay.Visibility = Visibility.Collapsed;
|
||||
|
||||
private async void AdminLoginModal_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var username = AdminUsernameBox.Text.Trim();
|
||||
var password = AdminPasswordBox.Password;
|
||||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return;
|
||||
|
||||
AdminLoginBtn.IsEnabled = false;
|
||||
AdminErrorLabel.Visibility = Visibility.Collapsed;
|
||||
|
||||
try
|
||||
{
|
||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
||||
var body = Newtonsoft.Json.JsonConvert.SerializeObject(new { username, password });
|
||||
var resp = await http.PostAsync($"{_config?.ServerUrl?.TrimEnd('/')}/api/auth/login",
|
||||
new StringContent(body, Encoding.UTF8, "application/json"));
|
||||
var json = await resp.Content.ReadAsStringAsync();
|
||||
var result = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(json, new { token = "" });
|
||||
|
||||
if (resp.IsSuccessStatusCode && !string.IsNullOrEmpty(result?.token))
|
||||
{
|
||||
_adminToken = result.token;
|
||||
AdminOverlay.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (_pendingExit)
|
||||
{
|
||||
_pendingExit = false;
|
||||
DoExit();
|
||||
return;
|
||||
}
|
||||
|
||||
AdminPanel.Visibility = Visibility.Visible;
|
||||
AdminUserLabel.Text = $"Angemeldet als {username}";
|
||||
AdminLoginButton.Content = "Admin-Modus beenden";
|
||||
}
|
||||
else
|
||||
{
|
||||
AdminErrorLabel.Text = "Ungültige Anmeldedaten";
|
||||
AdminErrorLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
AdminErrorLabel.Text = "Verbindung fehlgeschlagen";
|
||||
AdminErrorLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
finally { AdminLoginBtn.IsEnabled = true; }
|
||||
}
|
||||
|
||||
private void AdminPasswordBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Enter)
|
||||
AdminLoginModal_Click(sender, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private void AdminCheckin_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("net.exe", "stop \"IT Nexus Agent\"")
|
||||
{ CreateNoWindow = true, UseShellExecute = false })?.WaitForExit();
|
||||
Process.Start(new ProcessStartInfo("net.exe", "start \"IT Nexus Agent\"")
|
||||
{ CreateNoWindow = true, UseShellExecute = false });
|
||||
MessageBox.Show("Agent wird neu gestartet — Check-in in ~5 Sekunden.",
|
||||
"IT Nexus", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Fehler: {ex.Message}", "IT Nexus", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void AdminUpdates_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("UsoClient.exe", "StartScan") { CreateNoWindow = true });
|
||||
MessageBox.Show("Update-Scan gestartet.", "IT Nexus", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
private void AdminLog_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new LogWindow();
|
||||
dlg.Show();
|
||||
}
|
||||
|
||||
private void AdminRestart_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("net.exe", "stop \"IT Nexus Agent\"")
|
||||
{ CreateNoWindow = true, UseShellExecute = false })?.WaitForExit();
|
||||
Process.Start(new ProcessStartInfo("net.exe", "start \"IT Nexus Agent\"")
|
||||
{ CreateNoWindow = true, UseShellExecute = false });
|
||||
MessageBox.Show("Agent-Service wird neu gestartet.", "IT Nexus", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Fehler: {ex.Message}", "IT Nexus", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitTrayIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
var iconPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "icon.ico");
|
||||
var icon = File.Exists(iconPath)
|
||||
? new DrawingIcon(iconPath)
|
||||
: DrawingSystemIcons.Application;
|
||||
|
||||
_trayIcon = new WinForms.NotifyIcon
|
||||
{
|
||||
Icon = icon,
|
||||
Text = "IT Nexus Agent",
|
||||
Visible = true
|
||||
};
|
||||
|
||||
var menu = new WinForms.ContextMenuStrip();
|
||||
menu.Items.Add("Dashboard öffnen", null, (s, e) => ShowDashboard());
|
||||
menu.Items.Add("-");
|
||||
menu.Items.Add("IT Nexus Web", null, (s, e) =>
|
||||
{
|
||||
var url = _config?.ServerUrl ?? "https://it-nexus.cereda-systems.de";
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
});
|
||||
menu.Items.Add("-");
|
||||
menu.Items.Add("Beenden", null, (s, e) => ExitWithAuth());
|
||||
|
||||
_trayIcon.ContextMenuStrip = menu;
|
||||
_trayIcon.DoubleClick += (s, e) => ShowDashboard();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void ShowDashboard()
|
||||
{
|
||||
Show();
|
||||
WindowState = WindowState.Normal;
|
||||
Activate();
|
||||
Focus();
|
||||
}
|
||||
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void ExitWithAuth()
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (_adminToken != null)
|
||||
{
|
||||
DoExit();
|
||||
return;
|
||||
}
|
||||
|
||||
AdminUsernameBox.Text = "";
|
||||
AdminPasswordBox.Password = "";
|
||||
AdminErrorLabel.Visibility = Visibility.Collapsed;
|
||||
AdminOverlay.Visibility = Visibility.Visible;
|
||||
ShowDashboard();
|
||||
_pendingExit = true;
|
||||
AdminUsernameBox.Focus();
|
||||
});
|
||||
}
|
||||
|
||||
private bool _pendingExit = false;
|
||||
|
||||
private void DoExit()
|
||||
{
|
||||
_trayIcon!.Visible = false;
|
||||
System.Windows.Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private void TitleBar_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
|
||||
DragMove();
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
private bool _isMaximized = false;
|
||||
private double _restoreLeft, _restoreTop, _restoreWidth, _restoreHeight;
|
||||
|
||||
private void Minimize_Click(object sender, RoutedEventArgs e)
|
||||
=> WindowState = WindowState.Minimized;
|
||||
|
||||
private void Maximize_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isMaximized)
|
||||
RestoreWindow();
|
||||
else
|
||||
MaximizeWindow();
|
||||
}
|
||||
|
||||
private void MaximizeWindow()
|
||||
{
|
||||
_restoreLeft = Left; _restoreTop = Top;
|
||||
_restoreWidth = Width; _restoreHeight = Height;
|
||||
|
||||
var wa = SystemParameters.WorkArea;
|
||||
Left = wa.Left; Top = wa.Top;
|
||||
Width = wa.Width; Height = wa.Height;
|
||||
_isMaximized = true;
|
||||
|
||||
OuterBorder.CornerRadius = new CornerRadius(0);
|
||||
OuterBorder.Margin = new Thickness(0);
|
||||
OuterBorder.Effect = null;
|
||||
TitleBarBorder.CornerRadius = new CornerRadius(0);
|
||||
}
|
||||
|
||||
private void RestoreWindow()
|
||||
{
|
||||
Left = _restoreLeft; Top = _restoreTop;
|
||||
Width = _restoreWidth; Height = _restoreHeight;
|
||||
_isMaximized = false;
|
||||
|
||||
OuterBorder.CornerRadius = new CornerRadius(14);
|
||||
OuterBorder.Margin = new Thickness(16);
|
||||
OuterBorder.Effect = new System.Windows.Media.Effects.DropShadowEffect
|
||||
{
|
||||
Color = System.Windows.Media.Colors.Black,
|
||||
Opacity = 0.4, BlurRadius = 20, ShadowDepth = 0
|
||||
};
|
||||
TitleBarBorder.CornerRadius = new CornerRadius(14, 14, 0, 0);
|
||||
}
|
||||
|
||||
private void Window_StateChanged(object sender, EventArgs e) { }
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
31
agent-cs/UI/LogWindow.xaml
Normal file
31
agent-cs/UI/LogWindow.xaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<Window x:Class="ITNexusAgent.UI.LogWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="IT Nexus Agent – Log"
|
||||
Width="700" Height="500"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Background="#1E1E2E" FontFamily="Consolas">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="40"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox x:Name="LogText" Grid.Row="0"
|
||||
Background="#1E1E2E" Foreground="#CDD6F4"
|
||||
FontSize="11" IsReadOnly="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
BorderThickness="0" Padding="12"
|
||||
TextWrapping="NoWrap"/>
|
||||
<Border Grid.Row="1" Background="#181825" Padding="12,0">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Button Content="Aktualisieren" Height="26" Padding="10,0"
|
||||
Click="Refresh_Click" Background="#313244" Foreground="#CDD6F4"
|
||||
BorderThickness="0" FontFamily="Segoe UI" FontSize="11" Cursor="Hand"/>
|
||||
<Button Content="Log leeren" Height="26" Padding="10,0" Margin="8,0,0,0"
|
||||
Click="Clear_Click" Background="#313244" Foreground="#F38BA8"
|
||||
BorderThickness="0" FontFamily="Segoe UI" FontSize="11" Cursor="Hand"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
40
agent-cs/UI/LogWindow.xaml.cs
Normal file
40
agent-cs/UI/LogWindow.xaml.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Windows;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
|
||||
namespace ITNexusAgent.UI;
|
||||
|
||||
public partial class LogWindow : Window
|
||||
{
|
||||
private const string LogPath = @"C:\ProgramData\IT Nexus Agent\agent.log";
|
||||
|
||||
public LogWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
LoadLog();
|
||||
}
|
||||
|
||||
private void LoadLog()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(LogPath))
|
||||
{
|
||||
var lines = File.ReadAllLines(LogPath);
|
||||
LogText.Text = string.Join(Environment.NewLine, lines.TakeLast(500));
|
||||
LogText.ScrollToEnd();
|
||||
}
|
||||
else LogText.Text = "Log-Datei nicht gefunden.";
|
||||
}
|
||||
catch (Exception ex) { LogText.Text = $"Fehler: {ex.Message}"; }
|
||||
}
|
||||
|
||||
private void Refresh_Click(object sender, RoutedEventArgs e) => LoadLog();
|
||||
|
||||
private void Clear_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var r = MessageBox.Show("Log wirklich löschen?", "IT Nexus",
|
||||
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (r != MessageBoxResult.Yes) return;
|
||||
try { File.WriteAllText(LogPath, ""); LoadLog(); } catch { }
|
||||
}
|
||||
}
|
||||
22
agent-cs/UI/MessageDialog.xaml
Normal file
22
agent-cs/UI/MessageDialog.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<Window x:Class="ITNexusAgent.UI.MessageDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Nachricht an IT senden"
|
||||
Width="400" Height="220"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize" Background="White" FontFamily="Segoe UI">
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock Text="Nachricht an das IT-Team" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="#1F2937" Margin="0,0,0,12"/>
|
||||
<TextBox x:Name="MessageBox" Height="80" TextWrapping="Wrap" AcceptsReturn="True"
|
||||
Padding="8" FontSize="12" BorderBrush="#D1D5DB"
|
||||
VerticalScrollBarVisibility="Auto"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,0,0">
|
||||
<Button Content="Abbrechen" Width="80" Height="32" Margin="0,0,8,0"
|
||||
Click="Cancel_Click" Background="#F9FAFB" BorderBrush="#D1D5DB" FontSize="12"/>
|
||||
<Button Content="Senden" Width="80" Height="32"
|
||||
Click="Send_Click" Background="#0078D4" Foreground="White"
|
||||
BorderThickness="0" FontSize="12" FontWeight="SemiBold"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
18
agent-cs/UI/MessageDialog.xaml.cs
Normal file
18
agent-cs/UI/MessageDialog.xaml.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace ITNexusAgent.UI;
|
||||
|
||||
public partial class MessageDialog : Window
|
||||
{
|
||||
public string Message { get; private set; } = "";
|
||||
|
||||
public MessageDialog() => InitializeComponent();
|
||||
|
||||
private void Send_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Message = MessageBox.Text.Trim();
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
private void Cancel_Click(object sender, RoutedEventArgs e) => DialogResult = false;
|
||||
}
|
||||
108
agent-cs/UI/NotificationWindow.xaml
Normal file
108
agent-cs/UI/NotificationWindow.xaml
Normal file
@@ -0,0 +1,108 @@
|
||||
<Window x:Class="ITNexusAgent.UI.NotificationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="IT Nexus Mitteilung"
|
||||
Width="500" Height="Auto"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Topmost="True"
|
||||
FontFamily="Segoe UI">
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="ConfirmBtn" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Height" Value="46"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bg" CornerRadius="8"
|
||||
Background="{TemplateBinding Background}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bg" Property="Opacity" Value="0.85"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Bg" Property="Opacity" Value="0.7"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<!-- Äußerer Rahmen mit Schatten + farbigem Top-Border -->
|
||||
<Border CornerRadius="12" Background="#1A1D2E"
|
||||
BorderBrush="#2D3250" BorderThickness="1">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black" BlurRadius="32" ShadowDepth="8" Opacity="0.7"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="4"/> <!-- Farbige Top-Linie -->
|
||||
<RowDefinition Height="Auto"/> <!-- Header: Icon + Typ + Titel -->
|
||||
<RowDefinition Height="Auto"/> <!-- Nachricht -->
|
||||
<RowDefinition Height="Auto"/> <!-- Meta -->
|
||||
<RowDefinition Height="Auto"/> <!-- Button -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Farbige Linie oben (wie Web: border-top) -->
|
||||
<Border Grid.Row="0" x:Name="TopAccent" CornerRadius="12,12,0,0"/>
|
||||
|
||||
<!-- Header -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal"
|
||||
Margin="24,20,24,16" VerticalAlignment="Center">
|
||||
<!-- Icon-Box -->
|
||||
<Border x:Name="IconBox" Width="44" Height="44" CornerRadius="10"
|
||||
VerticalAlignment="Top" Margin="0,0,14,0">
|
||||
<TextBlock x:Name="IconText" FontSize="22"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<!-- Typ + Titel -->
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TypeLabel" FontSize="10" FontWeight="Bold"
|
||||
TextOptions.TextFormattingMode="Display"/>
|
||||
<TextBlock x:Name="TitleText" FontSize="17" FontWeight="Bold"
|
||||
Foreground="White" TextWrapping="Wrap" MaxWidth="370"
|
||||
Margin="0,3,0,0" LineHeight="22"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Nachricht -->
|
||||
<Border Grid.Row="2" Margin="24,0,24,16"
|
||||
Background="#252840" CornerRadius="8"
|
||||
BorderBrush="#3D4270" BorderThickness="1" Padding="14,12">
|
||||
<TextBlock x:Name="MessageText" FontSize="13" Foreground="#B0B8D1"
|
||||
TextWrapping="Wrap" LineHeight="20"/>
|
||||
</Border>
|
||||
|
||||
<!-- Meta -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal"
|
||||
Margin="24,0,24,20" Opacity="0.55">
|
||||
<TextBlock x:Name="MetaText" FontSize="11" Foreground="#8B9BBF"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Button + Hinweis -->
|
||||
<StackPanel Grid.Row="4" Margin="24,0,24,24">
|
||||
<Button x:Name="ConfirmButton" Style="{StaticResource ConfirmBtn}"
|
||||
Click="ConfirmButton_Click">
|
||||
<TextBlock Text="✓ Gelesen und bestätigt" FontSize="13" FontWeight="Bold"/>
|
||||
</Button>
|
||||
<TextBlock Text="Du musst diese Nachricht bestätigen um fortzufahren."
|
||||
FontSize="10" Foreground="#555E7A"
|
||||
HorizontalAlignment="Center" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
59
agent-cs/UI/NotificationWindow.xaml.cs
Normal file
59
agent-cs/UI/NotificationWindow.xaml.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using ITNexusAgent.Models;
|
||||
using ITNexusAgent.Services;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace ITNexusAgent.UI;
|
||||
|
||||
public partial class NotificationWindow : Window
|
||||
{
|
||||
private readonly Announcement _ann;
|
||||
private readonly ApiService? _api;
|
||||
|
||||
public NotificationWindow(Announcement ann)
|
||||
{
|
||||
InitializeComponent();
|
||||
_ann = ann;
|
||||
|
||||
var (hex, label, icon) = ann.Type switch
|
||||
{
|
||||
"warning" => ("#DC3545", "WICHTIGE WARNUNG", "⚠️"),
|
||||
"maintenance" => ("#E67E22", "WARTUNGSANKÜNDIGUNG", "🔧"),
|
||||
_ => ("#5865F2", "INFORMATION", "ℹ️"),
|
||||
};
|
||||
|
||||
var color = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(hex);
|
||||
var brush = new SolidColorBrush(color);
|
||||
var iconBg = new SolidColorBrush(System.Windows.Media.Color.FromArgb(40, color.R, color.G, color.B));
|
||||
|
||||
TopAccent.Background = brush;
|
||||
IconBox.Background = iconBg;
|
||||
IconBox.BorderBrush = new SolidColorBrush(System.Windows.Media.Color.FromArgb(80, color.R, color.G, color.B));
|
||||
IconBox.BorderThickness = new Thickness(1);
|
||||
IconText.Text = icon;
|
||||
TypeLabel.Text = label;
|
||||
TypeLabel.Foreground = brush;
|
||||
TitleText.Text = ann.Title;
|
||||
MessageText.Text = ann.Message;
|
||||
ConfirmButton.Background = brush;
|
||||
MetaText.Text = $"IT Nexus Mitteilung";
|
||||
|
||||
try
|
||||
{
|
||||
var cfg = AgentConfig.Load(@"C:\ProgramData\IT Nexus Agent\config.json");
|
||||
_api = new ApiService(cfg.ServerUrl, cfg.AgentKey);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_api != null)
|
||||
await _api.AckAnnouncementAsync(_ann.Id, Environment.MachineName);
|
||||
}
|
||||
catch { }
|
||||
Close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user