Initial commit: IT Nexus Web-App
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user