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