Files
IT-Nexus/agent-cs/UI/DashboardWindow.xaml.cs

504 lines
18 KiB
C#

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);
}
}