Kaynağa Gözat

Removed ability to add child folder to "All Items" in JobDocuments; fixed some Notifications panel refresh problems;
Server now saves the currently running services before it installs a new update, and then starts them again when restarting - the installer was supposed to do this, but it wasn't working.
Created a pie chart dashboard for projects.

Kenric Nugteren 2 yıl önce
ebeveyn
işleme
149d9499d1

+ 55 - 0
prs.desktop/Dashboards/JobDocumentStatusChart.xaml

@@ -0,0 +1,55 @@
+<UserControl x:Class="PRSDesktop.JobDocumentStatusChart"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PRSDesktop"
+             xmlns:sf="http://schemas.syncfusion.com/wpf"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition x:Name="ChartRow" Height="*"/>
+            <RowDefinition x:Name="LabelRow" Height="0"/>
+        </Grid.RowDefinitions>
+        <sf:SfChart x:Name="Chart" Grid.Row="0">
+            <sf:SfChart.Legend>
+                <sf:ChartLegend x:Name="Legend"
+                                Orientation="Vertical"
+                                DockPosition="Floating">
+                    <sf:ChartLegend.ItemTemplate>
+                        <DataTemplate>
+                            <StackPanel Margin="10,0,10,0" Orientation="Horizontal">
+                                <Rectangle Width="10" Height="10" Margin="2" Fill="{Binding Colour}"/>
+                                <TextBlock HorizontalAlignment="Center"
+                                           Margin="5,0,0,0"
+                                           VerticalAlignment="Center"
+                                           Text="{Binding Text}"/>
+                            </StackPanel>
+                        </DataTemplate>
+                    </sf:ChartLegend.ItemTemplate>
+                </sf:ChartLegend>
+            </sf:SfChart.Legend>
+            <sf:PieSeries x:Name="Pie" XBindingPath="Group" YBindingPath="Count" SegmentColorPath="Colour">
+                <sf:PieSeries.AdornmentsInfo>
+                    <sf:ChartAdornmentInfo ShowLabel="True"
+                                           SegmentLabelContent="LabelContentPath">
+                        <sf:ChartAdornmentInfo.LabelTemplate>
+                            <DataTemplate>
+                                <TextBlock>
+                                    <Run Text="{Binding Item.MileStoneCode}"/>
+                                    <Run Text=":"/>
+                                    <Run Text="{Binding Item.Status}"/>
+                                </TextBlock>
+                            </DataTemplate>
+                        </sf:ChartAdornmentInfo.LabelTemplate>
+                    </sf:ChartAdornmentInfo>
+                </sf:PieSeries.AdornmentsInfo>
+            </sf:PieSeries>
+        </sf:SfChart>
+        <Label x:Name="NoDataLabel" Grid.Row="1"
+               HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
+               HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
+               FontSize="30" Foreground="LightGray"/>
+    </Grid>
+</UserControl>

+ 379 - 0
prs.desktop/Dashboards/JobDocumentStatusChart.xaml.cs

@@ -0,0 +1,379 @@
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.WPF;
+using java.nio.file;
+using Syncfusion.Data.Extensions;
+using Syncfusion.UI.Xaml.Charts;
+using Syncfusion.Windows.Controls.Gantt.Chart;
+using Syncfusion.Windows.Tools.Controls;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.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.Navigation;
+using System.Windows.Shapes;
+
+namespace PRSDesktop
+{
+    public class JobDocumentItemViewModel
+    {
+        public string Group { get; set; }
+
+        public string MileStoneCode { get; set; }
+
+        public JobDocumentStatusChart.StatusType Status { get; set; }
+
+        public int Count { get; set; }
+
+        public SolidColorBrush Colour { get; set; }
+    }
+
+    public class JobDocumentStatusChartProperties : BaseObject, IDashboardProperties
+    {
+        public JobDocumentStatusChart.ItemType ItemType { get; set; } = JobDocumentStatusChart.ItemType.Sets;
+
+        public JobDocumentStatusChart.StatusType[]? StatusTypes { get; set; } = null;
+
+        public Guid[] MileStones { get; set; } = Array.Empty<Guid>();
+
+        public Guid JobID { get; set; }
+    }
+
+    public class JobDocumentStatusChartElement : DashboardElement<JobDocumentStatusChart, WidgetGroups.Projects, JobDocumentStatusChartProperties> { }
+
+    /// <summary>
+    /// Interaction logic for JobDocumentStatusChart.xaml
+    /// </summary>
+    public partial class JobDocumentStatusChart : UserControl,
+        IDashboardWidget<WidgetGroups.Projects, JobDocumentStatusChartProperties>,
+        IHeaderDashboard
+    {
+        private Dictionary<Guid, Color> MileStoneColours { get; set; }
+
+        private Dictionary<Guid, JobDocumentSetMileStoneType> MilestoneTypes { get; set; }
+        private List<KeyValuePair<Guid, string>> JobLookups { get; set; }
+
+        public JobDocumentStatusChartProperties Properties { get; set; }
+
+        public DashboardHeader Header { get; } = new();
+
+        public enum ItemType
+        {
+            Sets,
+            Documents
+        }
+
+        public enum StatusType
+        {
+            Failed,
+            Incomplete,
+            Submitted,
+            Approved
+        }
+
+        public JobDocumentStatusChart()
+        {
+            InitializeComponent();
+        }
+
+        public void Setup()
+        {
+            JobLookups = new Client<Job>()
+                .Query(null,
+                    LookupFactory.DefineColumns<Job>())
+                .Rows.Select(x => new KeyValuePair<Guid, string>(
+                    (Guid?)x["ID"] ?? Guid.Empty,
+                    LookupFactory.FormatLookup<Job>(x.ToDictionary(new[] { "ID" }), Array.Empty<string>())))
+                .ToList();
+            JobLookups.Insert(0, new(Guid.Empty, "Select Job"));
+
+            var pallete = new ChartColorModel().GetMetroBrushes()
+                .Select(x => (x as SolidColorBrush)?.Color)
+                .Where(x => x != null)
+                .Select(x => x!.Value).ToList();
+
+            MilestoneTypes = new Client<JobDocumentSetMileStoneType>()
+                .Query(null,
+                    new Columns<JobDocumentSetMileStoneType>(x => x.ID)
+                        .Add(x => x.Code)
+                        .Add(x => x.Description))
+                .ToList<JobDocumentSetMileStoneType>().ToDictionary(x => x.ID, x => x);
+
+            int i = 0;
+            MileStoneColours = MilestoneTypes.ToDictionary(x => x.Key, x => pallete[i++ % pallete.Count]);
+
+            SetupHeader();
+        }
+
+        #region Header
+
+        private ComboBox JobBox;
+        private ComboBox ItemTypeBox;
+        private ComboBoxAdv MilestoneBox;
+        private ComboBoxAdv StatusTypeBox;
+
+        private void SetupHeader()
+        {
+            JobBox = new ComboBox
+            {
+                Margin = new Thickness(5, 0, 0, 0)
+            };
+            JobBox.ItemsSource = JobLookups;
+            JobBox.SelectedValuePath = "Key";
+            JobBox.DisplayMemberPath = "Value";
+            JobBox.SelectedValue = Properties.JobID;
+            JobBox.SelectionChanged += JobBox_SelectionChanged;
+
+            ItemTypeBox = new ComboBox
+            {
+                Margin = new Thickness(5, 0, 0, 0)
+            };
+            ItemTypeBox.ItemsSource = Enum.GetValues<ItemType>();
+            ItemTypeBox.SelectedValue = Properties.ItemType;
+            ItemTypeBox.SelectionChanged += ItemTypeBox_SelectionChanged;
+
+            MilestoneBox = new ComboBoxAdv
+            {
+                VerticalAlignment = VerticalAlignment.Stretch,
+                VerticalContentAlignment = VerticalAlignment.Center,
+                IsEditable = false,
+                AllowMultiSelect = true,
+                Width = 150,
+                DefaultText = "Select Milestones",
+
+                Margin = new Thickness(5, 0, 0, 0)
+            };
+
+            var items = MilestoneTypes.ToDictionary(x => x.Key, x => $"{x.Value.Code}: {x.Value.Description}");
+            MilestoneBox.ItemsSource = items;
+            MilestoneBox.SelectedValuePath = "Key";
+            MilestoneBox.DisplayMemberPath = "Value";
+            MilestoneBox.SelectedItems = Properties.MileStones.Select(x => items.Where(y => x == y.Key).FirstOrDefault()).ToObservableCollection();
+            MilestoneBox.SelectionChanged += MileStoneBox_SelectionChanged;
+
+            StatusTypeBox = new ComboBoxAdv
+            {
+                VerticalAlignment = VerticalAlignment.Stretch,
+                VerticalContentAlignment = VerticalAlignment.Center,
+                IsEditable = false,
+                AllowMultiSelect = true,
+                Width = 150,
+                DefaultText = "Select Status",
+
+                Margin = new Thickness(5, 0, 0, 0)
+            };
+            StatusTypeBox.ItemsSource = Enum.GetValues<StatusType>();
+            StatusTypeBox.SelectedItems = GetStatusTypes().ToObservableCollection();
+            StatusTypeBox.SelectionChanged += StatusTypeBox_SelectionChanged;
+
+            Header.BeginUpdate()
+                .Add(JobBox)
+                .Add(ItemTypeBox)
+                .Add(StatusTypeBox)
+                .Add(MilestoneBox)
+                .EndUpdate();
+        }
+
+        private void MileStoneBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            Properties.MileStones = MilestoneBox.SelectedItems.Cast<KeyValuePair<Guid, string>>().Select(x => x.Key).ToArray();
+            Refresh();
+        }
+
+        private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            Properties.JobID = (Guid)JobBox.SelectedValue;
+            Refresh();
+        }
+
+        private void ItemTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            Properties.ItemType = (ItemType)ItemTypeBox.SelectedValue;
+            Refresh();
+        }
+
+        private void StatusTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            Properties.StatusTypes = StatusTypeBox.SelectedItems.Cast<StatusType>().OrderBy(x => x).ToArray();
+            Refresh();
+        }
+
+        #endregion
+
+        private void ClearView(string message = "No Data")
+        {
+            NoDataLabel.Content = message;
+
+            Pie.Visibility = Visibility.Collapsed;
+
+            ChartRow.Height = new GridLength(0);
+            LabelRow.Height = new GridLength(1, GridUnitType.Star);
+        }
+        private void ShowView()
+        {
+            Pie.Visibility = Visibility.Visible;
+
+            ChartRow.Height = new GridLength(1, GridUnitType.Star);
+            LabelRow.Height = new GridLength(0);
+        }
+
+        private StatusType[] GetStatusTypes()
+        {
+            Properties.StatusTypes ??= Enum.GetValues<StatusType>().OrderBy(x => x).ToArray();
+            return Properties.StatusTypes;
+        }
+
+        private StatusType ConvertStatus(JobDocumentSetMileStoneStatus status)
+        {
+            switch (status)
+            {
+                case JobDocumentSetMileStoneStatus.Approved:
+                    return StatusType.Approved;
+                case JobDocumentSetMileStoneStatus.Submitted:
+                    return StatusType.Submitted;
+                case JobDocumentSetMileStoneStatus.Rejected:
+                case JobDocumentSetMileStoneStatus.Cancelled:
+                    return StatusType.Failed;
+                case JobDocumentSetMileStoneStatus.NotStarted:
+                case JobDocumentSetMileStoneStatus.InProgress:
+                case JobDocumentSetMileStoneStatus.OnHold:
+                case JobDocumentSetMileStoneStatus.InfoRequired:
+                case JobDocumentSetMileStoneStatus.Unknown:
+                default:
+                    return StatusType.Incomplete;
+            }
+        }
+
+        private Filter<JobDocumentSetMileStone> GetStatusFilter()
+        {
+            var statusTypes = GetStatusTypes();
+
+            if (statusTypes.Length == 0)
+                return new Filter<JobDocumentSetMileStone>().All();
+
+            var statuses = new List<JobDocumentSetMileStoneStatus>();
+            if (statusTypes.Contains(StatusType.Incomplete))
+            {
+                statuses.Add(JobDocumentSetMileStoneStatus.NotStarted);
+                statuses.Add(JobDocumentSetMileStoneStatus.InProgress);
+                statuses.Add(JobDocumentSetMileStoneStatus.OnHold);
+                statuses.Add(JobDocumentSetMileStoneStatus.InfoRequired);
+                statuses.Add(JobDocumentSetMileStoneStatus.Unknown);
+            }
+            if (statusTypes.Contains(StatusType.Approved))
+            {
+                statuses.Add(JobDocumentSetMileStoneStatus.Approved);
+            }
+            if (statusTypes.Contains(StatusType.Submitted))
+            {
+                statuses.Add(JobDocumentSetMileStoneStatus.Submitted);
+            }
+            if (statusTypes.Contains(StatusType.Failed))
+            {
+                statuses.Add(JobDocumentSetMileStoneStatus.Rejected);
+                statuses.Add(JobDocumentSetMileStoneStatus.Cancelled);
+            }
+
+            if (statuses.Count == 0)
+                return new Filter<JobDocumentSetMileStone>().None();
+
+            //return new Filter<JobDocumentSetMileStone>(x => x.Status).InList(statuses.ToArray());
+            var filter = new Filter<JobDocumentSetMileStone>(x => x.Status).IsEqualTo(statuses[0]);
+            for(int i = 1; i < statuses.Count; ++i)
+            {
+                filter.Or(x => x.Status).IsEqualTo(statuses[i]);
+            }
+            return filter;
+        }
+
+        private Filter<JobDocumentSetMileStone> GetMileStoneFilter()
+        {
+            if (Properties.MileStones.Length == 0)
+                return new Filter<JobDocumentSetMileStone>().All();
+            return new Filter<JobDocumentSetMileStone>(x => x.Type.ID).InList(Properties.MileStones);
+        }
+
+        public void Refresh()
+        {
+            if (Properties.JobID == Guid.Empty)
+            {
+                ClearView("Please select a Job");
+                return;
+            }
+
+            var statusFilter = GetStatusFilter();
+            var milestoneFilter = GetMileStoneFilter();
+            var columns = new Columns<JobDocumentSetMileStone>(x => x.ID)
+                .Add(x => x.Type.ID)
+                .Add(x => x.Status);
+
+            if (Properties.ItemType == ItemType.Documents)
+            {
+                columns.Add(x => x.Attachments);
+            }
+
+            var milestones = new Client<JobDocumentSetMileStone>()
+                .Query(
+                    new Filter<JobDocumentSetMileStone>(x => x.DocumentSet.Job.ID).IsEqualTo(Properties.JobID)
+                        .And(statusFilter)
+                        .And(milestoneFilter),
+                    columns);
+            if (!milestones.Rows.Any())
+            {
+                ClearView();
+                return;
+            }
+
+            var grouping = milestones
+                .ToObjects<JobDocumentSetMileStone>()
+                .GroupBy(x => new { x.Type.ID, Status = ConvertStatus(x.Status) })
+                .OrderBy(x => x.Key.ID).ThenBy(x => x.Key.Status);
+
+            var statusTypes = GetStatusTypes();
+            if (statusTypes.Length == 0)
+                statusTypes = Enum.GetValues<StatusType>();
+
+            float i = 0f;
+            var statusShades = statusTypes.ToDictionary(x => x, x => -0.5f + (++i / statusTypes.Length) * 0.5f);
+
+            var data = grouping.Select(x =>
+            {
+                if (!MilestoneTypes.TryGetValue(x.Key.ID, out var milestoneType)) return null;
+                if (!MileStoneColours.TryGetValue(x.Key.ID, out var milestoneColour)) return null;
+
+                return new JobDocumentItemViewModel
+                {
+                    Group = $"{milestoneType.Code} : {x.Key.Status}",
+                    Count = Properties.ItemType == ItemType.Documents ? x.Sum(x => x.Attachments) : x.Count(),
+                    MileStoneCode = milestoneType.Code,
+                    Status = x.Key.Status,
+                    Colour = new SolidColorBrush(ImageUtils.AdjustBrightness(milestoneColour, statusShades[x.Key.Status]))
+                };
+            }).Where(x => x is not null).Select(x => x!).ToList();
+
+            Legend.ItemsSource = MilestoneTypes.Select(x =>
+            {
+                var colour = MileStoneColours[x.Key];
+                return new { Text = x.Value.Code, Colour = new SolidColorBrush(colour) };
+            }).ToList();
+
+            Pie.ItemsSource = data;
+
+            ShowView();
+        }
+
+        public void Shutdown()
+        {
+        }
+    }
+}

+ 2 - 1
prs.desktop/MainWindow.xaml.cs

@@ -3286,7 +3286,8 @@ namespace PRSDesktop
         
         private void CheckForUpdates()
         {
-            Update.CheckForUpdates(GetUpdateLocation, GetLatestVersion, GetReleaseNotes, GetInstaller, App.AutoUpdateSettings.Elevated, "PRSDesktopSetup.exe");
+            Update.CheckForUpdates(
+                GetUpdateLocation, GetLatestVersion, GetReleaseNotes, GetInstaller, null, App.AutoUpdateSettings.Elevated, "PRSDesktopSetup.exe");
         }
 
         #endregion

+ 18 - 1
prs.desktop/Panels/Jobs/DocumentSets/JobDocumentSetFolderTree.cs

@@ -2,10 +2,12 @@ using System;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Windows;
+using System.Windows.Controls;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
 using InABox.DynamicGrid;
+using InABox.WPF;
 
 namespace PRSDesktop
 {
@@ -18,8 +20,23 @@ namespace PRSDesktop
         {
             Options.AddRange(DynamicTreeOption.Add, DynamicTreeOption.Edit, DynamicTreeOption.Delete);
             MaxRowHeight = 60D;
+
+            OnContextMenuOpening += JobDocumentSetFolderTree_OnContextMenuOpening;
         }
-        
+
+        private void JobDocumentSetFolderTree_OnContextMenuOpening(DynamicTreeNode node, ContextMenu menu)
+        {
+            if(node.ID != CoreUtils.FullGuid)
+            {
+                menu.AddItem("Add Child Folder", null, node, AddChildFolder_Click);
+            }
+        }
+
+        private void AddChildFolder_Click(DynamicTreeNode node)
+        {
+            AddItem(node);
+        }
+
         protected override Expression<Func<JobDocumentSetFolder, Guid>> ID => x => x.ID;
         protected override Expression<Func<JobDocumentSetFolder, Guid>> ParentID => x => x.Parent.ID;
         protected override Expression<Func<JobDocumentSetFolder, string>> Description => x => x.Name;

+ 3 - 2
prs.desktop/Panels/Notifications/NotificationPanel.xaml.cs

@@ -136,7 +136,7 @@ public partial class NotificationPanel : UserControl, IPanel<Notification>
 
     public event DataModelUpdateEvent OnUpdateDataModel;
 
-    private Button CreateButton(string caption, BitmapImage umage, Func<Button, CoreRow[], bool> action)
+    private Button CreateButton(string caption, BitmapImage? umage, Func<Button, CoreRow[], bool> action)
     {
         var result = Notifications.AddButton(caption, umage, action);
         result.Width = 100.0F;
@@ -225,7 +225,8 @@ public partial class NotificationPanel : UserControl, IPanel<Notification>
         {
             if (IsInbox)
             {
-                Notifications.AddRow(notification);
+                //Notifications.AddRow(notification);
+                Notifications.Refresh(false, true);
             }
         });
     }

+ 1 - 1
prs.desktop/Panels/Notifications/NotificationUtils.cs

@@ -81,7 +81,7 @@ namespace PRSDesktop
             {
                 using (new WaitCursor())
                 {
-                    new Client<Notification>().Save(updates, "", (o, e) => { });
+                    new Client<Notification>().Save(updates, "");
                 }
 
                 return true;

+ 6 - 1
prs.server/Forms/Configuration.xaml.cs

@@ -117,9 +117,14 @@ namespace PRSServer
             return Update.GetRemoteFile($"{GetChannelLocation(location)}/PRSSetup.exe").RawBytes;
         }
 
+        private void BeforeUpdate()
+        {
+            Servers.BeforeUpdate();
+        }
+
         private void CheckForUpdates()
         {
-            Update.CheckForUpdates(GetUpdateLocation, GetLatestVersion, GetReleaseNotes, GetInstaller, true, "PRSSetup.exe");
+            Update.CheckForUpdates(GetUpdateLocation, GetLatestVersion, GetReleaseNotes, GetInstaller, BeforeUpdate, true, "PRSSetup.exe");
         }
 
         #endregion

+ 60 - 21
prs.server/Forms/ServerGrid.cs

@@ -35,9 +35,14 @@ using Method = RestSharp.Method;
 
 namespace PRSServer
 {
+    class ServerStartupSettings : BaseObject, LocalConfigurationSettings
+    {
+        public List<string> StartServers { get; set; } = new();
+    }
+
     public class ServerGrid : DynamicGrid<Server>
     {
-        private Task _monitor;
+        private Task? _monitor;
 
         private ConcurrentBag<ServiceController> _services = new();
         
@@ -80,7 +85,7 @@ namespace PRSServer
                 : DynamicMenuStatus.Hidden;
         }
 
-        private void CreateServerMenu(DynamicMenuColumn column, CoreRow row)
+        private void CreateServerMenu(DynamicMenuColumn column, CoreRow? row)
         {
             if (row == null)
                 return;
@@ -189,22 +194,58 @@ namespace PRSServer
             base.ShowHelp("Server_Configuration");
         }
 
-        #region Grid Handling
-
-        protected override void Reload(Filters<Server> criteria, Columns<Server> columns, ref SortOrder<Server> sort,
-            Action<CoreTable, Exception> action)
+        public void BeforeUpdate()
         {
-            var table = new CoreTable();
-            table.LoadColumns(typeof(Server));
-
             var sections = PRSService.GetConfiguration().LoadAll();
+            RefreshServices(sections);
 
+            var closed = new List<string>();
+            foreach (var service in _services)
+            {
+                if(service.Status == ServiceControllerStatus.Running)
+                {
+                    service.Stop();
+                    closed.Add(service.ServiceName);
+                }
+            }
+
+            var config = PRSService.GetConfiguration<ServerStartupSettings>();
+            var startupSettings = config.Load();
+            startupSettings.StartServers = closed;
+            config.Save(startupSettings);
+        }
+
+        #region Grid Handling
+
+        private void RefreshServices(Dictionary<string, ServerSettings> sections)
+        {
             Interlocked.Exchange(
                 ref _services,
                 new ConcurrentBag<ServiceController>(
                     ServiceController.GetServices().Where(x => sections.ContainsKey(x.ServiceName))
                 )
             );
+        }
+
+        protected override void Reload(Filters<Server> criteria, Columns<Server> columns, ref SortOrder<Server>? sort,
+            Action<CoreTable?, Exception?> action)
+        {
+
+            var table = new CoreTable();
+            table.LoadColumns(typeof(Server));
+
+            var sections = PRSService.GetConfiguration().LoadAll();
+
+            RefreshServices(sections);
+
+            var startupConfig = PRSService.GetConfiguration<ServerStartupSettings>();
+            var startupSettings = startupConfig.Load();
+            foreach (var startup in startupSettings.StartServers)
+            {
+                _services.FirstOrDefault(x => x.ServiceName == startup)?.Start();
+            }
+            startupSettings.StartServers.Clear();
+            startupConfig.Save(startupSettings);
 
             foreach (var section in sections.OrderBy(x => x.Value.Type))
             {
@@ -429,7 +470,7 @@ namespace PRSServer
                     (o, e) => base.ShowHelp("Automatic_Updates"))
 
             };
-            propertyEditor = new DynamicEditorForm(typeof(EditableAutoUpdateSettings), null, buttons);
+            var propertyEditor = new DynamicEditorForm(typeof(EditableAutoUpdateSettings), null, buttons);
             propertyEditor.OnDefineEditor += PropertyEditor_OnDefineEditor;
             propertyEditor.OnDefineLookups += sender => DefineLookups(sender, new Server[] { });
             propertyEditor.Items = new BaseObject[] { editable };
@@ -518,8 +559,6 @@ namespace PRSServer
                 }
         }
 
-        private DynamicEditorForm propertyEditor;
-
         public bool EditProperties(Type type, ServerProperties item, bool enabled)
         {
             var pages = new DynamicEditorPages();
@@ -540,7 +579,7 @@ namespace PRSServer
                         { UseShellExecute = true });
                 }
             );
-            propertyEditor = new DynamicEditorForm(type, pages, buttons);
+            var propertyEditor = new DynamicEditorForm(type, pages, buttons);
 
             if(type == typeof(DatabaseServerProperties))
             {
@@ -575,7 +614,7 @@ namespace PRSServer
             { (int)ServiceControllerStatus.Paused, Properties.Resources.pause.AsBitmapImage() }
         };
 
-        private BitmapImage StateImage(CoreRow arg)
+        private BitmapImage StateImage(CoreRow? arg)
         {
             if (arg == null)
                 return Properties.Resources.tick.AsBitmapImage();
@@ -584,7 +623,7 @@ namespace PRSServer
             return _stateimages[state];
         }
 
-        private FrameworkElement StateToolTip(DynamicActionColumn arg1, CoreRow arg2)
+        private FrameworkElement? StateToolTip(DynamicActionColumn arg1, CoreRow? arg2)
         {
             if (arg2 == null)
                 return null;
@@ -592,12 +631,12 @@ namespace PRSServer
             return arg1.TextToolTip(service != null ? "Current State: " + service.Status : "Not Installed");
         }
         
-        private FrameworkElement MenuToolTip(DynamicActionColumn arg1, CoreRow arg2)
+        private FrameworkElement? MenuToolTip(DynamicActionColumn arg1, CoreRow? arg2)
         {
             return arg2 != null ? arg1.TextToolTip("Server Options") : null;
         }
 
-        private bool StateAction(CoreRow arg)
+        private bool StateAction(CoreRow? arg)
         {
             if (arg == null)
                 return false;
@@ -672,7 +711,7 @@ namespace PRSServer
             { ServerType.Certificate, Properties.Resources.certificate.AsBitmapImage() }
         };
 
-        private BitmapImage TypeImage(CoreRow arg)
+        private BitmapImage TypeImage(CoreRow? arg)
         {
             if (arg == null)
                 return Properties.Resources.help.AsBitmapImage();
@@ -680,7 +719,7 @@ namespace PRSServer
             return _typeimages[type];
         }
         
-        private FrameworkElement TypeToolTip(DynamicActionColumn arg1, CoreRow arg2)
+        private FrameworkElement? TypeToolTip(DynamicActionColumn arg1, CoreRow? arg2)
         {
             if (arg2 == null)
                 return null;
@@ -694,7 +733,7 @@ namespace PRSServer
 
         #region Console Functions
 
-        private BitmapImage ConsoleImage(CoreRow arg)
+        private BitmapImage? ConsoleImage(CoreRow? arg)
         {
             if (arg == null)
                 return Properties.Resources.target.AsBitmapImage();
@@ -705,7 +744,7 @@ namespace PRSServer
             return null;
         }
 
-        private bool ConsoleAction(CoreRow arg)
+        private bool ConsoleAction(CoreRow? arg)
         {
             if (arg == null)
                 return false;

+ 6 - 3
prs.server/Services/PRSService.cs

@@ -55,13 +55,14 @@ namespace PRSServer
             return _servicename;
         }
 
-        public static LocalConfiguration<ServerSettings> GetConfiguration(string section = "")
+        public static LocalConfiguration<T> GetConfiguration<T>(string section = "")
+            where T : LocalConfigurationSettings, new()
         {
             // TODO : Remove the fallback location
-            var configuration = new LocalConfiguration<ServerSettings>(CoreUtils.GetCommonAppData(), section);
+            var configuration = new LocalConfiguration<T>(CoreUtils.GetCommonAppData(), section);
             if (!File.Exists(configuration.GetFileName()))
             {
-                var oldsettings = new LocalConfiguration<ServerSettings>(
+                var oldsettings = new LocalConfiguration<T>(
                     Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location),
                     ""
                 );
@@ -76,6 +77,8 @@ namespace PRSServer
             return configuration;
         }
 
+        public static LocalConfiguration<ServerSettings> GetConfiguration(string section = "") => GetConfiguration<ServerSettings>(section);
+
         public void Run(string servicename)
         {
             _servicename = servicename;

+ 2 - 0
prs.shared/Update.cs

@@ -78,6 +78,7 @@ namespace PRS.Shared
             Func<string, string> getLatestVersion,
             Func<string, string> getReleaseNotes,
             Func<string, byte[]?> getInstaller,
+            Action? beforeUpdate,
             bool elevated,
             string tempName)
         {
@@ -121,6 +122,7 @@ namespace PRS.Shared
                         bOK = true;
                     }
 
+                    beforeUpdate?.Invoke();
                     progress.Report("Launching Installer");
                     if (bOK)
                     {