504 lines
18 KiB
C#
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);
|
|
}
|
|
}
|