Merge pull request #6842 from duplicati/feature/added-crash-dialog-window

Add crash dialog
This commit is contained in:
Kenneth Skovhede
2026-04-16 13:56:30 +02:00
committed by GitHub
3 changed files with 182 additions and 2 deletions
@@ -0,0 +1,165 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Media;
using Avalonia.Layout;
using Avalonia.Themes.Fluent;
using ScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility;
namespace Duplicati.GUI.TrayIcon
{
/// <summary>
/// Helper class for showing error dialogs
/// </summary>
public static class ErrorDialog
{
/// <summary>
/// Shows an error dialog for the crash, being very defensive to avoid additional crashes.
/// This should only be called when the hosted server instance is crashing.
/// </summary>
/// <param name="ex">The exception to show</param>
public static void ShowErrorDialog(Exception ex)
{
try
{
// Build a simple Avalonia application to show the dialog
var builder = AppBuilder
.Configure<Application>()
.UsePlatformDetect();
var app = builder.SetupWithoutStarting().Instance;
if (app == null)
return;
// Apply a simple theme so controls look native
app.Styles.Add(new FluentTheme());
// Create the message box window
var window = new Window
{
Title = "Duplicati TrayIcon Error",
Width = 600,
Height = 400,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
CanResize = true,
ShowInTaskbar = false,
Topmost = true
};
// Create the content
var stackPanel = new StackPanel
{
Margin = new Thickness(20),
Spacing = 10
};
var titleBlock = new TextBlock
{
Text = "A fatal error occurred",
FontSize = 16,
FontWeight = FontWeight.Bold,
TextWrapping = TextWrapping.Wrap
};
var messageBlock = new TextBlock
{
Text = ex.Message,
TextWrapping = TextWrapping.Wrap
};
// Details label
var detailsLabel = new TextBlock
{
Text = "Details:",
FontWeight = FontWeight.Bold,
Margin = new Thickness(0, 10, 0, 0)
};
// Scrollable text area with full exception details
var exceptionText = new TextBox
{
Text = ex.ToString(),
IsReadOnly = true,
TextWrapping = TextWrapping.NoWrap,
AcceptsReturn = true,
FontFamily = new FontFamily("Consolas, Monaco, Courier New, monospace"),
FontSize = 10
};
// Explicitly wrap in a ScrollViewer for proper scrolling
var scrollViewer = new ScrollViewer
{
Content = exceptionText,
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Height = 200
};
var buttonPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 20, 0, 0)
};
var okButton = new Button
{
Content = "OK",
Width = 100,
HorizontalContentAlignment = HorizontalAlignment.Center
};
okButton.Click += (_, _) =>
{
try
{
window.Close();
}
catch
{
// Ignore errors closing window
}
};
buttonPanel.Children.Add(okButton);
stackPanel.Children.Add(titleBlock);
stackPanel.Children.Add(messageBlock);
stackPanel.Children.Add(detailsLabel);
stackPanel.Children.Add(scrollViewer);
stackPanel.Children.Add(buttonPanel);
window.Content = stackPanel;
app.Run(window);
}
catch
{
}
}
}
}
@@ -98,6 +98,22 @@ namespace Duplicati.GUI.TrayIcon
/// </summary>
[STAThread]
public static int Main(string[] _args)
{
try
{
return RunMain(_args);
}
catch (Exception ex)
{
Library.Crashlog.CrashlogHelper.LogCrashException(ex);
ErrorDialog.ShowErrorDialog(ex);
Environment.Exit(1);
return 1;
}
}
private static int RunMain(string[] _args)
{
PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, PackageHelper.NamedExecutable.TrayIcon);
var args = new List<string>(_args);
@@ -18,7 +18,6 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using Duplicati.Library.Crashlog;
namespace Duplicati.GUI.TrayIcon.Net10
{
@@ -26,6 +25,6 @@ namespace Duplicati.GUI.TrayIcon.Net10
public static class Program
{
public static int Main(string[] args)
=> CrashlogHelper.WrapWithCrashLog(() => Duplicati.GUI.TrayIcon.Program.Main(args));
=> Duplicati.GUI.TrayIcon.Program.Main(args);
}
}