first commit
This commit is contained in:
commit
43494f38bb
32 changed files with 1810 additions and 0 deletions
52
.gitignore
vendored
Normal file
52
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Build-Output
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Rider / JetBrains
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Paket-Manager
|
||||||
|
packages/
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
|
||||||
|
# Logs & Temporäres
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
*.cache
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Visual Studio Arbeitsordner
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Resharper
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
|
||||||
|
# Test Ergebnisse
|
||||||
|
TestResults/
|
||||||
|
*.trx
|
||||||
|
|
||||||
|
# Publish Output
|
||||||
|
publish/
|
||||||
|
out/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Sonstige
|
||||||
|
[Bb]uild/
|
||||||
|
project.lock.json
|
||||||
|
artifacts/
|
||||||
|
Generated Files/
|
||||||
22
InstallMonitor.sln
Normal file
22
InstallMonitor.sln
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.12.35527.113 d17.12
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InstallMonitor", "InstallMonitor\InstallMonitor.csproj", "{45E112A5-43DB-436E-86E1-F112A73F0B30}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{45E112A5-43DB-436E-86E1-F112A73F0B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{45E112A5-43DB-436E-86E1-F112A73F0B30}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{45E112A5-43DB-436E-86E1-F112A73F0B30}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{45E112A5-43DB-436E-86E1-F112A73F0B30}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
66
InstallMonitor/AddServerWindow.xaml
Normal file
66
InstallMonitor/AddServerWindow.xaml
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<Window x:Class="InstallMonitor.AddServerWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:InstallMonitor"
|
||||||
|
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="AddServerWindow" Height="450" Width="800">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="70"/>
|
||||||
|
<RowDefinition Height="10*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Margin="8,0,0,0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="5*"/>
|
||||||
|
<RowDefinition Height="5*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Grid.Row="0"
|
||||||
|
Text="Log Monitoring"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Grid.Row="1"
|
||||||
|
Text="Server hinzufügen"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="1" Background="#eaeaea">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="7*" />
|
||||||
|
<ColumnDefinition Width="220"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
|
||||||
|
<Grid Grid.Column="0" >
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="25"/>
|
||||||
|
<RowDefinition Height="10*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Label Grid.Row="0" Content="Server:" Margin="5,0,0,0"/>
|
||||||
|
<TextBox Grid.Row="1" Margin="10,0,0,10" x:Name="UserInput" TextWrapping="Wrap" AcceptsReturn="True"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<Button Width="100" Height="100" Click="Button_Click">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="8*"/>
|
||||||
|
<RowDefinition Height="2*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<fa:IconBlock Grid.Row="0" Icon="Add" Foreground="Green" FontSize="40" FontWeight="ExtraBold"/>
|
||||||
|
<TextBlock Grid.Row="1" Text="Add servers" FontSize="14"/>
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
50
InstallMonitor/AddServerWindow.xaml.cs
Normal file
50
InstallMonitor/AddServerWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für AddServerWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class AddServerWindow : Window
|
||||||
|
{
|
||||||
|
public event EventHandler? ServerAdd;
|
||||||
|
public AddServerWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RequestAddServer(string server)
|
||||||
|
{
|
||||||
|
ServerAdd?.Invoke(server, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Button_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var servers = UserInput.Text.Split("\n");
|
||||||
|
|
||||||
|
foreach (var server in servers)
|
||||||
|
{
|
||||||
|
var serverName = server.TrimEnd();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(serverName))
|
||||||
|
{
|
||||||
|
RequestAddServer(serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
InstallMonitor/App.xaml
Normal file
17
InstallMonitor/App.xaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Application x:Class="InstallMonitor.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls"
|
||||||
|
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
|
||||||
|
xmlns:local="clr-namespace:InstallMonitor"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
33
InstallMonitor/App.xaml.cs
Normal file
33
InstallMonitor/App.xaml.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
AppHost.ConfigureServices();
|
||||||
|
base.OnStartup(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AppHost
|
||||||
|
{
|
||||||
|
public static IServiceProvider Services { get; private set; }
|
||||||
|
|
||||||
|
public static void ConfigureServices()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
services.AddSingleton<ConfigService>();
|
||||||
|
|
||||||
|
Services = services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
InstallMonitor/AppSettings.cs
Normal file
21
InstallMonitor/AppSettings.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public List<string> Servers { get; set; } = new();
|
||||||
|
public int MaxRetries { get; set; } = 3;
|
||||||
|
public int RetryTimeout { get; set; } = 20000;
|
||||||
|
public int InstallationProgressTimeout { get; set; } = 15;
|
||||||
|
public string LogFileDirectory { get; set; } = @"\\{ServerName}\C$\Program Files\Schleupen\log";
|
||||||
|
public string LogFileNamePattern { get; set; } = @"CSdeploy_2*.log";
|
||||||
|
|
||||||
|
public string GetResolvedLogFilePath(string serverName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(LogFileDirectory) || string.IsNullOrWhiteSpace(serverName))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return LogFileDirectory.Replace("{ServerName}", serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
InstallMonitor/AssemblyInfo.cs
Normal file
10
InstallMonitor/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
71
InstallMonitor/ConfigService.cs
Normal file
71
InstallMonitor/ConfigService.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class ConfigService
|
||||||
|
{
|
||||||
|
private const string ConfigFileName = "appsettings.json";
|
||||||
|
|
||||||
|
public AppSettings Settings { get; private set; }
|
||||||
|
|
||||||
|
public ConfigService()
|
||||||
|
{
|
||||||
|
LoadOrCreateDefaultConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadOrCreateDefaultConfig()
|
||||||
|
{
|
||||||
|
if (!File.Exists(ConfigFileName))
|
||||||
|
{
|
||||||
|
Settings = new AppSettings
|
||||||
|
{
|
||||||
|
Servers = new List<string> { "Server1", "Server2" }
|
||||||
|
};
|
||||||
|
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var config = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(AppContext.BaseDirectory)
|
||||||
|
.AddJsonFile(ConfigFileName, optional: false, reloadOnChange: false)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Settings = config.Get<AppSettings>() ?? new AppSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||||
|
var json = JsonSerializer.Serialize(Settings, options);
|
||||||
|
File.WriteAllText(ConfigFileName, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsServer(string name) => Settings.Servers.Any(s => string.Equals(s, name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
public void AddServer(string name)
|
||||||
|
{
|
||||||
|
if (!ContainsServer(name))
|
||||||
|
{
|
||||||
|
Settings.Servers.Add(name);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveServer(string name)
|
||||||
|
{
|
||||||
|
var serverToRemove = Settings.Servers
|
||||||
|
.FirstOrDefault(s => string.Equals(s, name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (serverToRemove != null)
|
||||||
|
{
|
||||||
|
Settings.Servers.Remove(serverToRemove);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
41
InstallMonitor/Converters/BooleanToColorConverter.cs
Normal file
41
InstallMonitor/Converters/BooleanToColorConverter.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class BooleanToColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolVal)
|
||||||
|
{
|
||||||
|
return boolVal
|
||||||
|
? Brushes.Green
|
||||||
|
: Brushes.Red;
|
||||||
|
}
|
||||||
|
return Brushes.Red;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InverseBooleanToColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolVal)
|
||||||
|
{
|
||||||
|
return boolVal
|
||||||
|
? Brushes.Red
|
||||||
|
: Brushes.Green;
|
||||||
|
}
|
||||||
|
return Brushes.Green;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
InstallMonitor/Converters/BooleanToIconConverter.cs
Normal file
31
InstallMonitor/Converters/BooleanToIconConverter.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using FontAwesome.Sharp;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class BooleanToIconConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool isOk = value is bool b && b;
|
||||||
|
return isOk ? IconChar.CheckCircle : IconChar.ExclamationCircle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InverseBooleanToIconConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool isOk = value is bool b && b;
|
||||||
|
return isOk ? IconChar.ExclamationCircle : IconChar.CheckCircle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class BooleanToTextDecorationConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolVal)
|
||||||
|
{
|
||||||
|
return boolVal
|
||||||
|
? TextDecorations.Strikethrough
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InverseBooleanToTextDecorationConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolVal)
|
||||||
|
{
|
||||||
|
return boolVal
|
||||||
|
? null
|
||||||
|
: TextDecorations.Strikethrough;
|
||||||
|
}
|
||||||
|
return TextDecorations.Strikethrough;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
InstallMonitor/Converters/BooleanToVisibilityConverter.cs
Normal file
29
InstallMonitor/Converters/BooleanToVisibilityConverter.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
[ValueConversion(typeof(bool), typeof(Visibility))]
|
||||||
|
public class BooleanToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool b)
|
||||||
|
return b ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
return Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InverseBoolToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> (value is bool b && !b) ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
=> throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class DateTimeToLocalizedStringConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is DateTime dt)
|
||||||
|
{
|
||||||
|
string format = parameter as string ?? "f";
|
||||||
|
return dt.ToString(format, new CultureInfo("de-de"));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
InstallMonitor/InstallMonitor.csproj
Normal file
32
InstallMonitor/InstallMonitor.csproj
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ApplicationIcon>schleupengrc_logo.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="schleupengrc_logo.jpg" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="schleupengrc_logo.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FontAwesome.Sharp" Version="6.6.0" />
|
||||||
|
<PackageReference Include="MahApps.Metro" Version="2.4.10" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.6" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="schleupengrc_logo.jpg" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
43
InstallMonitor/LogEntry.cs
Normal file
43
InstallMonitor/LogEntry.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public record LogEntry : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public string RawLine { get; set; }
|
||||||
|
public string? Phase { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public bool IsStepCompletion { get; set; }
|
||||||
|
public bool IsError { get; set; } = false;
|
||||||
|
private bool _isIgnored = false;
|
||||||
|
public bool IsIgnored { get => _isIgnored; set { _isIgnored = value; OnPropertyChanged(); } }
|
||||||
|
public int CurrentStep { get; set; }
|
||||||
|
public int TotalSteps { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public TimeSpan Duration => DateTime.Now - Timestamp;
|
||||||
|
|
||||||
|
public event EventHandler? ServerCardIgnored;
|
||||||
|
public event EventHandler? PhaseGroupIgnored;
|
||||||
|
|
||||||
|
public void Ignore()
|
||||||
|
{
|
||||||
|
var handle = ServerCardIgnored;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
handle = PhaseGroupIgnored;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
50
InstallMonitor/LogParser.cs
Normal file
50
InstallMonitor/LogParser.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public static class LogParser
|
||||||
|
{
|
||||||
|
public static LogEntry ParseLogLine(string line)
|
||||||
|
{
|
||||||
|
var result = new LogEntry { RawLine = line };
|
||||||
|
|
||||||
|
var timestampMatch = Regex.Match(line, @"^(?<ts>\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2})");
|
||||||
|
if (timestampMatch.Success)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParseExact(timestampMatch.Groups["ts"].Value, "dd.MM.yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var timestamp))
|
||||||
|
{
|
||||||
|
result.Timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.Substring(timestampMatch.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var phaseMatch = Regex.Match(line, @"^\s+(DeployCS-[^:]+):$");
|
||||||
|
if (phaseMatch.Success)
|
||||||
|
{
|
||||||
|
result.Phase = phaseMatch.Groups[1].Value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stepMatch = Regex.Match(line, @"^\s+\[Schritt (\d+)/(\d+) beendet\.\]$");
|
||||||
|
if (stepMatch.Success)
|
||||||
|
{
|
||||||
|
result.IsStepCompletion = true;
|
||||||
|
result.CurrentStep = int.Parse(stepMatch.Groups[1].Value);
|
||||||
|
result.TotalSteps = int.Parse(stepMatch.Groups[2].Value);
|
||||||
|
result.Message = line;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorMatch = Regex.Match(line, @"^\s+Fehler\: (.+)$");
|
||||||
|
if (errorMatch.Success)
|
||||||
|
{
|
||||||
|
result.IsError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Message = line;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
InstallMonitor/LogPhaseGroup.cs
Normal file
71
InstallMonitor/LogPhaseGroup.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class LogPhaseGroup : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public string Phase { get; set; } = string.Empty;
|
||||||
|
public bool HasErrors => Entries.Any(e => e.IsError && !e.IsIgnored);
|
||||||
|
public bool HasIgnoredErrors => Entries.Any(e => e.IsError && e.IsIgnored);
|
||||||
|
public bool HasOnlyIgnoredErrors => HasIgnoredErrors && !HasErrors;
|
||||||
|
public bool HasEntries => Entries.Count > 0;
|
||||||
|
public int CurrentPhase { get; set; } = 0;
|
||||||
|
public int TotalPhases { get; set; } = 0;
|
||||||
|
public bool IsValidPhase => CurrentPhase > 0 && TotalPhases > 0;
|
||||||
|
public bool IsFinalPhase => IsValidPhase && CurrentPhase == TotalPhases;
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public TimeSpan Duration => DateTime.Now - Timestamp;
|
||||||
|
public TimeSpan? LastActivity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsValidPhase)
|
||||||
|
{
|
||||||
|
var duration = Duration;
|
||||||
|
|
||||||
|
if (HasEntries)
|
||||||
|
{
|
||||||
|
return Entries.Last().Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<LogEntry> Entries { get; } = new();
|
||||||
|
public string StepLabel => IsValidPhase
|
||||||
|
? $"Schritt {CurrentPhase} von {TotalPhases}"
|
||||||
|
: "Ermittle Schritte ...";
|
||||||
|
|
||||||
|
public string Steps => IsValidPhase
|
||||||
|
? $"({CurrentPhase} / {TotalPhases})"
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
public int Progress => IsValidPhase
|
||||||
|
? (int)(((double)CurrentPhase / TotalPhases) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
public void AddEntry(LogEntry entry)
|
||||||
|
{
|
||||||
|
entry.PhaseGroupIgnored += OnLogEntryErrorIgnored;
|
||||||
|
Entries.Add(entry);
|
||||||
|
OnPropertyChanged(nameof(HasOnlyIgnoredErrors));
|
||||||
|
OnPropertyChanged(nameof(HasErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogEntryErrorIgnored(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(HasOnlyIgnoredErrors));
|
||||||
|
OnPropertyChanged(nameof(HasErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
142
InstallMonitor/LogReader.cs
Normal file
142
InstallMonitor/LogReader.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public class LogReader
|
||||||
|
{
|
||||||
|
private CancellationTokenSource _cts;
|
||||||
|
private ConfigService _configService;
|
||||||
|
private long _lastPosition = 0;
|
||||||
|
private int _retryCount = 0;
|
||||||
|
public LogReader(ConfigService config, CancellationTokenSource cts)
|
||||||
|
{
|
||||||
|
_configService = config;
|
||||||
|
_cts = cts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Directory => _configService.Settings.LogFileDirectory;
|
||||||
|
public string FilePattern => _configService.Settings.LogFileNamePattern;
|
||||||
|
public int MaxRetries => _configService.Settings.MaxRetries;
|
||||||
|
public int RetryTimeout => _configService.Settings.RetryTimeout;
|
||||||
|
public string LogFile { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public event EventHandler? InternalError;
|
||||||
|
public event EventHandler? ReadLine;
|
||||||
|
public event EventHandler? StreamOpen;
|
||||||
|
public event EventHandler? AwaitLine;
|
||||||
|
public event EventHandler? TaskFailed;
|
||||||
|
|
||||||
|
private void FindLogFile()
|
||||||
|
{
|
||||||
|
var file = new DirectoryInfo(Directory)
|
||||||
|
.GetFiles(FilePattern)
|
||||||
|
.OrderByDescending(f => f.Name)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
LogFile = file.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(LogFile))
|
||||||
|
throw new IOException("Datei nicht gefunden");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReadLogAsync()
|
||||||
|
{
|
||||||
|
while (!_cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FindLogFile();
|
||||||
|
|
||||||
|
using var fs = new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
using var reader = new StreamReader(fs, System.Text.Encoding.UTF8, true);
|
||||||
|
|
||||||
|
StreamOpened();
|
||||||
|
|
||||||
|
if (_lastPosition == 0)
|
||||||
|
{
|
||||||
|
string? line;
|
||||||
|
while ((line = await reader.ReadLineAsync()) != null)
|
||||||
|
{
|
||||||
|
LineRead(line);
|
||||||
|
}
|
||||||
|
_lastPosition = fs.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!_cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
fs.Seek(_lastPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
string? line;
|
||||||
|
while ((line = await reader.ReadLineAsync()) != null)
|
||||||
|
{
|
||||||
|
LineRead(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastPosition = fs.Position;
|
||||||
|
await Task.Delay(200, _cts.Token);
|
||||||
|
|
||||||
|
LineAwaited();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex) { }
|
||||||
|
catch (Exception ex) when (_retryCount < MaxRetries)
|
||||||
|
{
|
||||||
|
InternalErrorCaught($"Versuch: ({_retryCount + 1}/{MaxRetries}) - {ex.Message}");
|
||||||
|
_retryCount++;
|
||||||
|
await Task.Delay(RetryTimeout, _cts.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
TaskHasFailed($"Versuch: (no more retries) - {ex.Message}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StreamOpened()
|
||||||
|
{
|
||||||
|
_retryCount = 0;
|
||||||
|
|
||||||
|
var handle = StreamOpen;
|
||||||
|
if(handle != null)
|
||||||
|
handle.Invoke(null, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LineRead(string line)
|
||||||
|
{
|
||||||
|
var handle = ReadLine;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Invoke(line, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LineAwaited()
|
||||||
|
{
|
||||||
|
var handle = AwaitLine;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Invoke(null, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InternalErrorCaught(string message)
|
||||||
|
{
|
||||||
|
var handle = InternalError;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Invoke(message, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TaskHasFailed(string message)
|
||||||
|
{
|
||||||
|
var handle = InternalError;
|
||||||
|
if (handle != null)
|
||||||
|
handle.Invoke(message, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
InstallMonitor/LogTreeView.xaml
Normal file
80
InstallMonitor/LogTreeView.xaml
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<Window x:Class="InstallMonitor.LogTreeView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
|
||||||
|
xmlns:local="clr-namespace:InstallMonitor"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="LogTreeView" Height="900" Width="1200">
|
||||||
|
<Window.Resources>
|
||||||
|
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||||
|
<local:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisibilityConverter"/>
|
||||||
|
<local:BooleanToIconConverter x:Key="BooleanToIconConverter"/>
|
||||||
|
<local:InverseBooleanToIconConverter x:Key="InverseBooleanToIconConverter"/>
|
||||||
|
<local:BooleanToColorConverter x:Key="BooleanToColorConverter"/>
|
||||||
|
<local:InverseBooleanToColorConverter x:Key="InverseBooleanToColorConverter"/>
|
||||||
|
<local:BooleanToTextDecorationConverter x:Key="BooleanToTextDecorationConverter"/>
|
||||||
|
<local:InverseBooleanToTextDecorationConverter x:Key="InverseBooleanToTextDecorationConverter"/>
|
||||||
|
<local:DateTimeToLocalizedStringConverter x:Key="DateTimeConverter"/>
|
||||||
|
</Window.Resources>
|
||||||
|
<Grid>
|
||||||
|
<TreeView ItemsSource="{Binding}">
|
||||||
|
<TreeView.ItemTemplate>
|
||||||
|
<HierarchicalDataTemplate ItemsSource="{Binding Entries}">
|
||||||
|
<Grid VerticalAlignment="Center">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="20"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<fa:IconBlock
|
||||||
|
Icon="ScrewdriverWrench"
|
||||||
|
Visibility="{Binding HasOnlyIgnoredErrors, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
|
Foreground="#1e3050"/>
|
||||||
|
|
||||||
|
<fa:IconBlock
|
||||||
|
Icon="{Binding HasErrors, Converter={StaticResource InverseBooleanToIconConverter}, FallbackValue=CheckCircle}"
|
||||||
|
Foreground="{Binding HasErrors, Converter={StaticResource InverseBooleanToColorConverter}, FallbackValue=Green}"
|
||||||
|
Visibility="{Binding HasOnlyIgnoredErrors, Converter={StaticResource InverseBoolToVisibilityConverter}}"/>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding Phase}" FontWeight="Bold" Grid.Column="1"/>
|
||||||
|
<TextBlock Text="{Binding Steps}" Margin="8,0,0,0" Foreground="Gray" Grid.Column="2"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel
|
||||||
|
Orientation="Horizontal" Margin="4,2">
|
||||||
|
<StackPanel.ContextMenu>
|
||||||
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||||
|
<MenuItem
|
||||||
|
Header="Fehler ignorieren"
|
||||||
|
Click="IgnoreError_Click"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Visibility="{Binding IsError, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</StackPanel.ContextMenu>
|
||||||
|
<fa:IconBlock
|
||||||
|
Icon="ScrewdriverWrench"
|
||||||
|
Visibility="{Binding IsIgnored, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
|
Foreground="#1e3050"/>
|
||||||
|
<fa:IconBlock
|
||||||
|
Icon="{Binding IsError, Converter={StaticResource InverseBooleanToIconConverter}, FallbackValue=CheckCircle}"
|
||||||
|
Foreground="{Binding IsError, Converter={StaticResource InverseBooleanToColorConverter}, FallbackValue=Green}"
|
||||||
|
Visibility="{Binding IsIgnored, Converter={StaticResource InverseBoolToVisibilityConverter}}"/>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding Timestamp, Converter={StaticResource DateTimeConverter}, ConverterParameter='ddd, dd MMMM yyyy H:mm:ss'}"
|
||||||
|
TextDecorations="{Binding IsIgnored, Converter={StaticResource BooleanToTextDecorationConverter}}"
|
||||||
|
Foreground="Gray"
|
||||||
|
Margin="8,0,8,0"/>
|
||||||
|
<TextBlock Text="{Binding Message}" TextDecorations="{Binding IsIgnored, Converter={StaticResource BooleanToTextDecorationConverter}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</HierarchicalDataTemplate.ItemTemplate>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</TreeView.ItemTemplate>
|
||||||
|
</TreeView>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
43
InstallMonitor/LogTreeView.xaml.cs
Normal file
43
InstallMonitor/LogTreeView.xaml.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using WinForms = System.Windows.Forms;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaktionslogik für LogTreeView.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class LogTreeView : Window
|
||||||
|
{
|
||||||
|
public LogTreeView(ObservableCollection<LogPhaseGroup> phaseGroups)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = phaseGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IgnoreError_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem menuItem && menuItem.CommandParameter is LogEntry entry)
|
||||||
|
{
|
||||||
|
WinForms.DialogResult dr = WinForms.MessageBox.Show("Do you want to ignore this error?", "Are you sure?", WinForms.MessageBoxButtons.YesNoCancel, WinForms.MessageBoxIcon.Warning);
|
||||||
|
|
||||||
|
if (dr == WinForms.DialogResult.Yes)
|
||||||
|
{
|
||||||
|
entry.Ignore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
InstallMonitor/MainWindow.xaml
Normal file
76
InstallMonitor/MainWindow.xaml
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<Window x:Class="InstallMonitor.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:InstallMonitor"
|
||||||
|
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
|
||||||
|
Title="Server Monitor"
|
||||||
|
Width="1200" Height="920"
|
||||||
|
Background="White"
|
||||||
|
Closing="Window_Closing">
|
||||||
|
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<!-- Layout in 2 Zeilen: Hauptbereich (90%) + Footer (10%) -->
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="75" />
|
||||||
|
<RowDefinition Height="8.75*" />
|
||||||
|
<RowDefinition Height="50" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="Server hinzufügen" Click="MenuItem_AddServers"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</Grid.ContextMenu>
|
||||||
|
|
||||||
|
<Border Grid.Row="0" Background="#f3f3f3" Padding="25,10">
|
||||||
|
<Grid>
|
||||||
|
<Grid Width="60" HorizontalAlignment="Left">
|
||||||
|
<Image Source="/schleupengrc_logo.jpg"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Margin="70,0,0,0" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="Schleupen.CS" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<TextBlock Text="Log-Monitoring" FontSize="16" FontWeight="SemiBold" Margin="0,25,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid HorizontalAlignment="Right">
|
||||||
|
<Button
|
||||||
|
x:Name="StartButton"
|
||||||
|
Click="StartMonitor_Click"
|
||||||
|
Width="50"
|
||||||
|
Height="50"
|
||||||
|
FontSize="25"
|
||||||
|
Content="{fa:Icon Play, Foreground=Green}" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
x:Name="StopButton"
|
||||||
|
Click="StopMonitor_Click"
|
||||||
|
Visibility="Hidden"
|
||||||
|
Width="50"
|
||||||
|
Height="50"
|
||||||
|
FontSize="25"
|
||||||
|
Content="{fa:Icon Stop, Foreground=Red}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Hauptcontainer mit ScrollViewer -->
|
||||||
|
<Border Grid.Row="1" Background="#eaeaea">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<WrapPanel Margin="20"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
ItemWidth="360"
|
||||||
|
ItemHeight="160"
|
||||||
|
x:Name="ServerCards"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
</WrapPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Footer mit Logo/Text -->
|
||||||
|
<Border Grid.Row="2" Background="#f3f3f3" Padding="10">
|
||||||
|
<TextBlock Text="© 2025 by DURI" FontSize="16" FontWeight="SemiBold"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
117
InstallMonitor/MainWindow.xaml.cs
Normal file
117
InstallMonitor/MainWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public ConfigService ConfigService = AppHost.Services.GetRequiredService<ConfigService>();
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
foreach (var serverName in ConfigService.Settings.Servers)
|
||||||
|
{
|
||||||
|
AddServerCard(serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Window_Closing(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
await StopMonitor();
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartMonitor()
|
||||||
|
{
|
||||||
|
StopButton.Visibility = Visibility.Visible;
|
||||||
|
StartButton.Visibility = Visibility.Hidden;
|
||||||
|
|
||||||
|
foreach (ServerCard server in ServerCards.Children)
|
||||||
|
{
|
||||||
|
server.ViewModel.StartMonitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopMonitor()
|
||||||
|
{
|
||||||
|
StopButton.Visibility = Visibility.Hidden;
|
||||||
|
StartButton.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
var stopTasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (ServerCard server in ServerCards.Children)
|
||||||
|
{
|
||||||
|
if (server.ViewModel.IsMonitoring)
|
||||||
|
{
|
||||||
|
var task = server.ViewModel.StopMonitor();
|
||||||
|
stopTasks.Add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(stopTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MenuItem_AddServers(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var viewer = new AddServerWindow();
|
||||||
|
viewer.Title = "Server hinzufügen";
|
||||||
|
viewer.ServerAdd += OnServerAdd;
|
||||||
|
viewer.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerDelete(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ServerCardViewModel server)
|
||||||
|
{
|
||||||
|
var serverCard = ServerCards.Children.OfType<ServerCard>().First(s => s.DataContext.Equals(server));
|
||||||
|
ServerCards.Children.Remove(serverCard);
|
||||||
|
ConfigService.RemoveServer(server.ServerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerAdd(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is string serverName)
|
||||||
|
{
|
||||||
|
if (!ConfigService.ContainsServer(serverName))
|
||||||
|
{
|
||||||
|
AddServerCard(serverName);
|
||||||
|
ConfigService.AddServer(serverName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddServerCard(string serverName)
|
||||||
|
{
|
||||||
|
var server = new ServerCard
|
||||||
|
{
|
||||||
|
DataContext = new ServerCardViewModel(ConfigService)
|
||||||
|
{
|
||||||
|
ServerName = serverName,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
server.ViewModel.Deleted += OnServerDelete;
|
||||||
|
ServerCards.Children.Add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartMonitor_Click(object sender, RoutedEventArgs e) => StartMonitor();
|
||||||
|
public async void StopMonitor_Click(object sender, RoutedEventArgs e) => await StopMonitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>bin\Release\net8.0-windows\publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<_TargetId>Folder</_TargetId>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
125
InstallMonitor/ServerCard.xaml
Normal file
125
InstallMonitor/ServerCard.xaml
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<UserControl
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:fa="http://schemas.awesome.incremented/wpf/xaml/fontawesome.sharp"
|
||||||
|
xmlns:local="clr-namespace:InstallMonitor"
|
||||||
|
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" x:Class="InstallMonitor.ServerCard"
|
||||||
|
Width="340" Height="150" Background="White">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||||
|
<local:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisibilityConverter"/>
|
||||||
|
<local:BooleanToColorConverter x:Key="BooleanToColorConverter"/>
|
||||||
|
<local:InverseBooleanToColorConverter x:Key="InverseBooleanToColorConverter"/>
|
||||||
|
<local:BooleanToIconConverter x:Key="BooleanToIconConverter"/>
|
||||||
|
<local:InverseBooleanToIconConverter x:Key="InverseBooleanToIconConverter"/>
|
||||||
|
<Style TargetType="{x:Type ProgressBar}" BasedOn="{StaticResource {x:Type ProgressBar}}"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Border Background="White" CornerRadius="4" Width="340" Height="150" MinWidth="100" MinHeight="100"
|
||||||
|
BorderBrush="#ddd" BorderThickness="1">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="8" ShadowDepth="2" Opacity="0.2" />
|
||||||
|
</Border.Effect>
|
||||||
|
<Grid Margin="12" Background="White">
|
||||||
|
<Grid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Click="MenuItem_ShowLogDetails" Header="Details anzeigen"/>
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Click="MenuItem_StartMonitor" Header="Monitoring starten"/>
|
||||||
|
<MenuItem Click="MenuItem_StopMonitor" Header="Monitoring stoppen"/>
|
||||||
|
<MenuItem Click="MenuItem_RequestDelete" Header="Server löschen"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="Server neustarten"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</Grid.ContextMenu>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Background="White" >
|
||||||
|
<Grid HorizontalAlignment="Left">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="{Binding ServerName, FallbackValue=ServerName}" FontSize="20" FontWeight="Bold" />
|
||||||
|
<fa:IconBlock
|
||||||
|
Visibility="{Binding IsInstallationProgressTimeout, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Visible}"
|
||||||
|
FontSize="20"
|
||||||
|
Foreground="Gray"
|
||||||
|
Icon="HourglassHalf"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid HorizontalAlignment="Right">
|
||||||
|
<Grid Height="30"
|
||||||
|
Visibility="{Binding IsMonitoring, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
|
||||||
|
<Grid Visibility="{Binding IsInternalError, Converter={StaticResource InverseBoolToVisibilityConverter}, FallbackValue=Hidden}">
|
||||||
|
<Grid
|
||||||
|
Visibility="{Binding IsInitializing, Converter={StaticResource InverseBoolToVisibilityConverter}}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="35"/>
|
||||||
|
<ColumnDefinition Width="35"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding ErrorsCount, FallbackValue=23}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Visibility="{Binding HasErrors, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
|
FontSize="20" Grid.Column="0"/>
|
||||||
|
<fa:IconBlock
|
||||||
|
FontSize="30"
|
||||||
|
Icon="{Binding HasErrors, Converter={StaticResource InverseBooleanToIconConverter}, FallbackValue=CheckCircle}"
|
||||||
|
Foreground="{Binding HasErrors, Converter={StaticResource InverseBooleanToColorConverter}, FallbackValue=Green}"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Visibility="{Binding IsInitializing, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||||
|
<mah:ProgressRing IsActive="{Binding IsInitializing}" Width="30" Height="30" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid Visibility="{Binding IsInternalError, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Visible}">
|
||||||
|
<fa:IconBlock
|
||||||
|
FontSize="30"
|
||||||
|
Foreground="#ffc107"
|
||||||
|
Icon="TriangleExclamation"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="{Binding PhaseLabelText, FallbackValue=CurrentPhase}" VerticalAlignment="Top" Height="36" Width="314" Margin="0,43,0,0"/>
|
||||||
|
<TextBlock
|
||||||
|
FontSize="12"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
fa:Awesome.Inline="{Binding StepLabelText, FallbackValue=CurrentStepLabel}"
|
||||||
|
VerticalAlignment="Top" Height="15" Width="314" Margin="0,27,0,0"/>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ProgressBar Value="{Binding CurrentProgress, FallbackValue=30, Mode=OneWay}"
|
||||||
|
Style="{x:Null}"
|
||||||
|
Height="12"
|
||||||
|
VerticalAlignment="Center" Grid.ColumnSpan="2"
|
||||||
|
Foreground="{Binding HasProblems, Converter={StaticResource InverseBooleanToColorConverter}, FallbackValue=Green}"
|
||||||
|
Visibility="{Binding IsMonitoring, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
|
BorderThickness="0"
|
||||||
|
IsIndeterminate="{Binding IsProgressBarIsIndeterminate}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
59
InstallMonitor/ServerCard.xaml.cs
Normal file
59
InstallMonitor/ServerCard.xaml.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using WinForm = System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public partial class ServerCard : UserControl
|
||||||
|
{
|
||||||
|
public ServerCard()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerCardViewModel ViewModel
|
||||||
|
{
|
||||||
|
get => (ServerCardViewModel)DataContext;
|
||||||
|
set => DataContext = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_ShowLogDetails(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
var viewer = new LogTreeView(vm.LogPhaseGroups);
|
||||||
|
viewer.Title = $"Details - {vm.ServerName} - Install Monitor";
|
||||||
|
viewer.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void MenuItem_StartMonitor(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
vm.StartMonitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void MenuItem_StopMonitor(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
await vm.StopMonitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_RequestDelete(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
WinForm.DialogResult dr = WinForm.MessageBox.Show($"Do you want to remove {vm.ServerName}?", "Are you sure?", WinForm.MessageBoxButtons.YesNoCancel, WinForm.MessageBoxIcon.Warning);
|
||||||
|
|
||||||
|
if (dr == WinForm.DialogResult.Yes)
|
||||||
|
{
|
||||||
|
vm.RequestDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
263
InstallMonitor/ServerCardViewModel.cs
Normal file
263
InstallMonitor/ServerCardViewModel.cs
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public enum ServerStatus
|
||||||
|
{
|
||||||
|
Stopped,
|
||||||
|
Initializing,
|
||||||
|
InternalError,
|
||||||
|
Running,
|
||||||
|
RunningWithErrors,
|
||||||
|
InstallationProgressTimeout,
|
||||||
|
CompletedWithErrors,
|
||||||
|
Completed,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerCardViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public ServerCardViewModel(ConfigService config)
|
||||||
|
{
|
||||||
|
Config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ServerName { get; set; } = string.Empty;
|
||||||
|
public ObservableCollection<LogEntry> ErrorCollection { get; set; } = new();
|
||||||
|
public ObservableCollection<LogPhaseGroup> LogPhaseGroups { get; set; } = new()
|
||||||
|
{
|
||||||
|
new LogPhaseGroup
|
||||||
|
{
|
||||||
|
Phase = "Init Phase"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public CancellationTokenSource Cts = new CancellationTokenSource();
|
||||||
|
public Task? MonitoringTask;
|
||||||
|
public ConfigService Config;
|
||||||
|
public event EventHandler? Deleted;
|
||||||
|
|
||||||
|
public string InternalError
|
||||||
|
{
|
||||||
|
get => _internalError;
|
||||||
|
set { _internalError = value; OnPropertyChanged(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ServerStatus Status
|
||||||
|
{
|
||||||
|
get => _status;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_status != value)
|
||||||
|
{
|
||||||
|
_status = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(IsProgressBarIsIndeterminate));
|
||||||
|
OnPropertyChanged(nameof(HasErrors));
|
||||||
|
OnPropertyChanged(nameof(HasProblems));
|
||||||
|
OnPropertyChanged(nameof(ErrorsCount));
|
||||||
|
OnPropertyChanged(nameof(IsMonitoring));
|
||||||
|
OnPropertyChanged(nameof(IsInitializing));
|
||||||
|
OnPropertyChanged(nameof(IsInternalError));
|
||||||
|
OnPropertyChanged(nameof(IsInstallationProgressTimeout));
|
||||||
|
OnPropertyChanged(nameof(IsStopped));
|
||||||
|
OnPropertyChanged(nameof(StepLabelText));
|
||||||
|
OnPropertyChanged(nameof(PhaseLabelText));
|
||||||
|
|
||||||
|
if (HasMonitored)
|
||||||
|
{
|
||||||
|
Cts.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsProgressBarIsIndeterminate => !HasStepInfo || IsInternalError;
|
||||||
|
|
||||||
|
public bool IsStopped => Status == ServerStatus.Stopped;
|
||||||
|
public bool IsInitializing => Status == ServerStatus.Initializing;
|
||||||
|
public bool IsInternalError => Status == ServerStatus.InternalError;
|
||||||
|
public bool IsRunning => Status == ServerStatus.Running;
|
||||||
|
public bool IsRunningWithErrors => Status == ServerStatus.RunningWithErrors;
|
||||||
|
public bool IsInstallationProgressTimeout => Status == ServerStatus.InstallationProgressTimeout;
|
||||||
|
public bool IsCompletedWithErrors => Status == ServerStatus.CompletedWithErrors;
|
||||||
|
public bool IsCompleted => Status == ServerStatus.Completed;
|
||||||
|
public bool IsFailed => Status == ServerStatus.Failed;
|
||||||
|
public bool IsMonitoring => !IsStopped && !IsCompleted && !IsCompletedWithErrors && !IsFailed;
|
||||||
|
public bool HasMonitored => IsCompleted || IsCompletedWithErrors || IsFailed;
|
||||||
|
|
||||||
|
public bool HasStepInfo => LogPhaseGroups.Last().TotalPhases > 0;
|
||||||
|
public bool HasPhase => LogPhaseGroups.Count > 1;
|
||||||
|
|
||||||
|
public bool HasProblems => HasErrors || IsInternalError;
|
||||||
|
|
||||||
|
public bool HasErrors => ErrorCollection.Count > 0;
|
||||||
|
public int ErrorsCount => ErrorCollection.Count;
|
||||||
|
|
||||||
|
|
||||||
|
public LogPhaseGroup CurrentPhaseGroup => LogPhaseGroups.Last();
|
||||||
|
public string CurrentPhase => CurrentPhaseGroup.Phase;
|
||||||
|
public int CurrentProgress => CurrentPhaseGroup.Progress;
|
||||||
|
public string CurrentStepLabel => CurrentPhaseGroup.StepLabel;
|
||||||
|
public LogEntry CurrentLogEntry => CurrentPhaseGroup.Entries.Last();
|
||||||
|
|
||||||
|
public void CheckInstallationProgressTimeout()
|
||||||
|
{
|
||||||
|
TimeSpan? lastActivity = CurrentPhaseGroup.LastActivity;
|
||||||
|
|
||||||
|
if (lastActivity != null)
|
||||||
|
{
|
||||||
|
if (lastActivity.Value.TotalMinutes > Config.Settings.InstallationProgressTimeout)
|
||||||
|
{
|
||||||
|
Status = ServerStatus.InstallationProgressTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnAwaitLine(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
this.CheckInstallationProgressTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStreamOpen(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
this.ResetInternalError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnLogReadLine(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is string line)
|
||||||
|
{
|
||||||
|
this.AddLog(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnTaskFailed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is string message)
|
||||||
|
{
|
||||||
|
this.SetInternalError(message);
|
||||||
|
Status = ServerStatus.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnInternalError(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is string message)
|
||||||
|
{
|
||||||
|
this.SetInternalError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnIgnoreError(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is LogEntry error)
|
||||||
|
{
|
||||||
|
if (ErrorCollection.Contains(error))
|
||||||
|
{
|
||||||
|
ErrorCollection.Remove(error);
|
||||||
|
|
||||||
|
error.IsIgnored = true;
|
||||||
|
|
||||||
|
if (!HasErrors)
|
||||||
|
{
|
||||||
|
if (IsMonitoring)
|
||||||
|
Status = ServerStatus.Running;
|
||||||
|
else Status = ServerStatus.Completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StepLabelText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsStopped)
|
||||||
|
return "Monitoring stopped";
|
||||||
|
|
||||||
|
if (IsInternalError)
|
||||||
|
return "Internal Error";
|
||||||
|
|
||||||
|
if (IsInitializing && !HasStepInfo)
|
||||||
|
return "Initializing steps ...";
|
||||||
|
|
||||||
|
if (IsRunning)
|
||||||
|
return CurrentStepLabel;
|
||||||
|
|
||||||
|
if (IsRunningWithErrors && HasStepInfo)
|
||||||
|
return CurrentStepLabel;
|
||||||
|
|
||||||
|
if (IsInstallationProgressTimeout)
|
||||||
|
return $"Last activity {(int)CurrentPhaseGroup.LastActivity!.Value.TotalMinutes} minutes ago";
|
||||||
|
|
||||||
|
if (IsCompleted)
|
||||||
|
return ":Check: Completed";
|
||||||
|
|
||||||
|
if (IsCompletedWithErrors)
|
||||||
|
return $":TriangleExclamation: Completed with {ErrorsCount} error(s).";
|
||||||
|
|
||||||
|
if (IsFailed)
|
||||||
|
return ":Xmark: Task failed";
|
||||||
|
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PhaseLabelText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsStopped)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
if (IsInternalError)
|
||||||
|
return InternalError;
|
||||||
|
|
||||||
|
if (IsInitializing && !HasPhase)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
if (IsInitializing && HasPhase)
|
||||||
|
return CurrentPhase;
|
||||||
|
|
||||||
|
if (IsRunning)
|
||||||
|
return CurrentPhase;
|
||||||
|
|
||||||
|
if (IsRunningWithErrors && HasPhase)
|
||||||
|
return CurrentPhase;
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshProgress()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(StepLabelText));
|
||||||
|
OnPropertyChanged(nameof(PhaseLabelText));
|
||||||
|
OnPropertyChanged(nameof(CurrentProgress));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void RequestDelete()
|
||||||
|
{
|
||||||
|
await this.StopMonitor();
|
||||||
|
Deleted?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
private ServerStatus _status = ServerStatus.Stopped;
|
||||||
|
|
||||||
|
private string _internalError = string.Empty;
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
24
InstallMonitor/ServerCardViewModelInternalErrorExtensions.cs
Normal file
24
InstallMonitor/ServerCardViewModelInternalErrorExtensions.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public static class ServerCardViewModelInternalErrorExtensions
|
||||||
|
{
|
||||||
|
public static void ResetInternalError(this ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
vm.InternalError = string.Empty;
|
||||||
|
vm.Status = ServerStatus.Initializing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetInternalError(this ServerCardViewModel vm, string message)
|
||||||
|
{
|
||||||
|
vm.InternalError = message;
|
||||||
|
vm.Status = ServerStatus.InternalError;
|
||||||
|
vm.RefreshProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
InstallMonitor/ServerCardViewModelLogExtensions.cs
Normal file
103
InstallMonitor/ServerCardViewModelLogExtensions.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public static class ServerCardViewModelLogExtensions
|
||||||
|
{
|
||||||
|
public static LogReader GetLogReader(this ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
var logReader = new LogReader(vm.Config, vm.Cts);
|
||||||
|
|
||||||
|
logReader.InternalError += vm.OnInternalError;
|
||||||
|
logReader.AwaitLine += vm.OnAwaitLine;
|
||||||
|
logReader.ReadLine += vm.OnLogReadLine;
|
||||||
|
logReader.StreamOpen += vm.OnStreamOpen;
|
||||||
|
logReader.TaskFailed += vm.OnTaskFailed;
|
||||||
|
|
||||||
|
return logReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddLog(this ServerCardViewModel vm, string logLine)
|
||||||
|
{
|
||||||
|
var entry = LogParser.ParseLogLine(logLine);
|
||||||
|
entry.ServerCardIgnored += vm.OnIgnoreError;
|
||||||
|
|
||||||
|
var currentPhase = vm.LogPhaseGroups.Last();
|
||||||
|
|
||||||
|
if (entry.Phase != null)
|
||||||
|
{
|
||||||
|
var nextPhase = new LogPhaseGroup { Phase = entry.Phase };
|
||||||
|
nextPhase.Timestamp = entry.Timestamp;
|
||||||
|
|
||||||
|
if (currentPhase.CurrentPhase > 0 &&
|
||||||
|
currentPhase.TotalPhases > 0)
|
||||||
|
{
|
||||||
|
nextPhase.CurrentPhase = currentPhase.CurrentPhase + 1;
|
||||||
|
nextPhase.TotalPhases = currentPhase.TotalPhases;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.AddLogPhase(nextPhase);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.IsStepCompletion)
|
||||||
|
{
|
||||||
|
currentPhase.CurrentPhase = entry.CurrentStep;
|
||||||
|
currentPhase.TotalPhases = entry.TotalSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(entry.Message))
|
||||||
|
return;
|
||||||
|
|
||||||
|
vm.AddLogPhaseEntry(currentPhase, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddLogPhase(this ServerCardViewModel vm, LogPhaseGroup logPhase)
|
||||||
|
{
|
||||||
|
if (logPhase.CurrentPhase > 0 &&
|
||||||
|
logPhase.TotalPhases > 0 &&
|
||||||
|
vm.IsInitializing)
|
||||||
|
vm.Status = ServerStatus.Running;
|
||||||
|
|
||||||
|
App.Current.Dispatcher.Invoke(() => vm.LogPhaseGroups.Add(logPhase));
|
||||||
|
|
||||||
|
vm.RefreshProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddLogPhaseEntry(this ServerCardViewModel vm, LogPhaseGroup currentPhase, LogEntry entry)
|
||||||
|
{
|
||||||
|
if (entry.IsError)
|
||||||
|
{
|
||||||
|
vm.Status = ServerStatus.RunningWithErrors;
|
||||||
|
vm.ErrorCollection.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.IsInstallationProgressTimeout)
|
||||||
|
{
|
||||||
|
if (vm.HasErrors)
|
||||||
|
vm.Status = ServerStatus.RunningWithErrors;
|
||||||
|
else vm.Status = ServerStatus.Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.IsStepCompletion && currentPhase.IsFinalPhase)
|
||||||
|
{
|
||||||
|
if (vm.HasErrors)
|
||||||
|
vm.Status = ServerStatus.CompletedWithErrors;
|
||||||
|
else
|
||||||
|
vm.Status = ServerStatus.Completed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.IsStepCompletion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
App.Current.Dispatcher.Invoke(() => currentPhase.AddEntry(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
InstallMonitor/ServerCardViewModelMonitorExtensions.cs
Normal file
58
InstallMonitor/ServerCardViewModelMonitorExtensions.cs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ControlzEx.Standard;
|
||||||
|
|
||||||
|
namespace InstallMonitor
|
||||||
|
{
|
||||||
|
public static class ServerCardViewModelMonitorExtensions
|
||||||
|
{
|
||||||
|
public static void StartMonitor(this ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
if (vm.IsMonitoring)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vm.ClearMonitor();
|
||||||
|
|
||||||
|
var logReader = vm.GetLogReader();
|
||||||
|
|
||||||
|
vm.MonitoringTask = Task.Run(async () => await logReader.ReadLogAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task StopMonitor(this ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
vm.Cts.Cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (vm.MonitoringTask != null)
|
||||||
|
await vm.MonitoringTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
|
||||||
|
vm.Status = ServerStatus.Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearMonitor(this ServerCardViewModel vm)
|
||||||
|
{
|
||||||
|
vm.ErrorCollection = new();
|
||||||
|
|
||||||
|
vm.LogPhaseGroups = new()
|
||||||
|
{
|
||||||
|
new LogPhaseGroup
|
||||||
|
{
|
||||||
|
Phase = "Init Phase"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.ResetInternalError();
|
||||||
|
|
||||||
|
vm.Cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
vm.Status = ServerStatus.Initializing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
InstallMonitor/schleupengrc_logo.ico
Normal file
BIN
InstallMonitor/schleupengrc_logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
BIN
InstallMonitor/schleupengrc_logo.jpg
Normal file
BIN
InstallMonitor/schleupengrc_logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
Loading…
Add table
Add a link
Reference in a new issue