Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,26 @@
<MicaBackdrop />
</Window.SystemBackdrop>

<Grid Padding="12">
<Grid Padding="12" x:Name="GridRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="{x:Bind Service.Name, Mode=OneWay}" FontSize="18" FontWeight="SemiBold" />
<TextBlock Text="{x:Bind Agent.Endpoint.DisplayName, Mode=OneWay}" FontSize="12" Foreground="Gray"/>
<TextBlock Text="{x:Bind Service.Description, Mode=OneWay}" TextWrapping="Wrap" Margin="0,6,0,0"/>
<TextBlock Text="{x:Bind Service.Name, Mode=OneWay}" FontSize="18" Style="{ThemeResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind Client.Endpoint.DisplayName, Mode=OneWay}" FontSize="12" Style="{ThemeResource CaptionTextBlockStyle}"/>
<TextBlock Text="{x:Bind Service.Description, Mode=OneWay}" TextWrapping="Wrap" Margin="0,6,0,0" Style="{ThemeResource BodyTextBlockStyle}"/>
<DropDownButton Content="Actions" Grid.Column="2" VerticalAlignment="Center">
<DropDownButton.Flyout>
<MenuFlyout Placement="Bottom">
<MenuFlyoutItem Text="Start" Click="ServiceStartMenuItem_Click" Padding="12,8"/>
<MenuFlyoutItem Text="Stop" Click="ServiceStopMenuItem_Click" Padding="12,8"/>
<MenuFlyoutItem Text="Restart" Click="ServiceRestartMenuItem_Click" Padding="12,8"/>
<MenuFlyoutItem Text="Enable" Click="ServiceEnableMenuItem_Click" Padding="12,8"/>
<MenuFlyoutItem Text="Disable" Click="ServiceDisableMenuItem_Click" Padding="12,8"/>
</MenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
</StackPanel>
<ScrollView
x:Name="LogsScrollView"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using paradigm_ehb.CommandCenter.Core;
using paradigm_ehb.CommandCenter.Core.Models;
using Services.V3;
using System;
using System.Collections.Generic;
using System.ComponentModel;
Expand All @@ -20,6 +22,7 @@
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Media.Protection.PlayReady;

namespace paradigm_ehb.CommandCenter.WinUI.srvMgnt.Views
{
Expand All @@ -28,7 +31,7 @@ namespace paradigm_ehb.CommandCenter.WinUI.srvMgnt.Views
/// </summary>
public sealed partial class ServiceDetailsWindow : Window
{
public AgentClient Agent { get; }
public AgentClient Client { get; }
public ServiceInfo Service { get; }

private CancellationTokenSource? _cts;
Expand All @@ -37,7 +40,7 @@ public sealed partial class ServiceDetailsWindow : Window
public ServiceDetailsWindow(AgentClient agentClient, ServiceInfo serviceInfo)
{
InitializeComponent();
Agent = agentClient;
Client = agentClient;
Service = serviceInfo;

ExtendsContentIntoTitleBar = true;
Expand All @@ -50,15 +53,182 @@ public ServiceDetailsWindow(AgentClient agentClient, ServiceInfo serviceInfo)
this.Closed += (_, _) => _cts?.Cancel();
}

private async void ServiceStartMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;
ServiceInfo serviceInfo = Service;

UnitActionReply response = await Client!.Service.PerformUnitActionAsync(new UnitActionRequest
{
UnitName = serviceInfo.Name,
Action = UnitActionRequest.Types.UnitAction.Start
});

if (response.Success)
{
await ShowInfoBarAsync("Service Action Result", $"Successfully started the service!", InfoBarSeverity.Success);
await UpdateServiceVisualStateAsync(serviceInfo, "started");
await Task.Delay(500); // brief delay to allow logs to populate
await GetLogsAsync(CancellationToken.None);
}
else
{
await ShowErrorInfoBarAsync(response.ErrorMessage ?? "Unknown error");
}
}

private async void ServiceStopMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;

ServiceInfo serviceInfo = (ServiceInfo)menuFlyoutItem.DataContext;

ContentDialog dialog = new()
{
XamlRoot = this.Content.XamlRoot,
Title = $"Are you sure you want to Stop {serviceInfo.Name}?",
CloseButtonText = "Cancel",
PrimaryButtonText = "Stop",
};

ContentDialogResult result = await dialog.ShowAsync();

if (result == ContentDialogResult.Primary)
{
UnitActionReply response = await Client!.Service.PerformUnitActionAsync(request: new UnitActionRequest
{
UnitName = serviceInfo.Name,
Action = UnitActionRequest.Types.UnitAction.Stop
});

if (response.Success)
{
await ShowInfoBarAsync("Service Action Result", $"Successfully stopped the service!", InfoBarSeverity.Success);
await UpdateServiceVisualStateAsync(serviceInfo, "stopped");
await Task.Delay(500); // brief delay to allow logs to populate
await GetLogsAsync(CancellationToken.None);
}
else
{
await ShowErrorInfoBarAsync(response.ErrorMessage ?? "Unknown error");
}
}
else
{
// Cancel, do nothing
}

}

private async void ServiceRestartMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;

ServiceInfo serviceInfo = Service;

ContentDialog dialog = new()
{
XamlRoot = this.Content.XamlRoot,
Title = $"Are you sure you want to Restart {serviceInfo.Name}?",
CloseButtonText = "Cancel",
PrimaryButtonText = "Restart",
};

ContentDialogResult result = await dialog.ShowAsync();

if (result == ContentDialogResult.Primary)
{
try
{
UnitActionReply response = await Client!.Service.PerformUnitActionAsync(new UnitActionRequest
{
UnitName = serviceInfo.Name,
Action = UnitActionRequest.Types.UnitAction.Restart
});

if (response.Success)
{
await ShowInfoBarAsync("Service Action Result", $"Successfully restarted the service!", InfoBarSeverity.Success);
await UpdateServiceVisualStateAsync(serviceInfo, "restarted");
await Task.Delay(500); // brief delay to allow logs to populate
await GetLogsAsync(CancellationToken.None);
}
else
{
await ShowErrorInfoBarAsync(response.ErrorMessage ?? "Unknown error");
}
}
catch (Exception ex)
{
if (ex is Grpc.Core.RpcException && serviceInfo.Name == "agent.service")
{
await ShowInfoBarAsync("Service Action Result", $"Successfully restarted the Agent!", InfoBarSeverity.Success);
}
else
{
await ShowErrorInfoBarAsync($"Exception during restart: {ex.Message}");
}
}
}
}

private async void ServiceEnableMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;
ServiceInfo serviceInfo = Service;

UnitFileActionReply response = await Client!.Service.PerformUnitFileActionAsync(new UnitFileActionRequest
{
UnitName = serviceInfo.Name,
Action = UnitFileActionRequest.Types.UnitFileAction.Enable
});

if (response.Success)
{
await ShowInfoBarAsync("Service Action Result", $"Successfully enabled the service!", InfoBarSeverity.Success);
await UpdateServiceVisualStateAsync(serviceInfo, "enabled");
await Task.Delay(500); // brief delay to allow logs to populate
await GetLogsAsync(CancellationToken.None);
}
else
{
await ShowErrorInfoBarAsync(response.ErrorMessage ?? "Unknown error");
}
}

private async void ServiceDisableMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;
ServiceInfo serviceInfo = Service;

UnitFileActionReply response = await Client!.Service.PerformUnitFileActionAsync(new UnitFileActionRequest
{
UnitName = serviceInfo.Name,
Action = UnitFileActionRequest.Types.UnitFileAction.Disable
});

if (response.Success)
{
await ShowInfoBarAsync("Service Action Result", $"Successfully disabled the service!", InfoBarSeverity.Success);
await UpdateServiceVisualStateAsync(serviceInfo, "disabled");
await Task.Delay(500); // brief delay to allow logs to populate
await GetLogsAsync(CancellationToken.None);
}
else
{
await ShowErrorInfoBarAsync(response.ErrorMessage ?? "Unknown error");
}
}

private async Task GetLogsAsync(CancellationToken cancellationToken)
{
if (Agent == null) return;
if (Client == null) return;

AsyncServerStreamingCall<JournalChunk>? call = null;

try
{
call = Agent.Journal.Action(new Journal.V1.JournalRequest()
call = Client.Journal.Action(new Journal.V1.JournalRequest()
{
NumFromTail = 50,
Field = Journal.V1.JournalRequest.Types.Field.Systemd,
Expand Down Expand Up @@ -152,5 +322,81 @@ private async Task GetLogsAsync(CancellationToken cancellationToken)
}
}
}

private async Task UpdateServiceVisualStateAsync(ServiceInfo serviceInfo, string action)
{
await DispatcherQueue.EnqueueAsync(() =>
{
switch (action)
{
case "enabled":
serviceInfo.State = "enabled";
serviceInfo.StateFill = (SolidColorBrush)Application.Current.Resources["SystemFillColorAttentionBrush"];
break;
case "disabled":
serviceInfo.State = "disabled";
serviceInfo.StateFill = (SolidColorBrush)Application.Current.Resources["SystemFillColorCriticalBrush"];
break;
case "started":
serviceInfo.ActiveState = "started";
serviceInfo.ActiveStateFill = (SolidColorBrush)Application.Current.Resources["SystemFillColorSuccessBrush"];
break;
case "restarted":
serviceInfo.ActiveState = "restarted";
serviceInfo.ActiveStateFill = (SolidColorBrush)Application.Current.Resources["SystemFillColorCautionBrush"];
break;
case "stopped":
serviceInfo.ActiveState = "stopped";
serviceInfo.ActiveStateFill = (SolidColorBrush)Application.Current.Resources["SystemFillColorCriticalBrush"];
break;
default:
break;
}
});
}

private async Task ShowInfoBarAsync(string title, string message, InfoBarSeverity severity)
{
// Ensure UI thread
await DispatcherQueue.EnqueueAsync(() =>
{
if (GridRoot == null)
{
// fallback to page XamlRoot; but InfoBar must be in visual tree to be visible
// if GridRoot is not available the InfoBar won't be shown persistently.
return;
}

InfoBar infoBar = new()
{
Title = title,
Message = message,
Severity = severity,
IsOpen = true,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(0, 0, 0, 0)
};

// Add to visual tree so it is visible
GridRoot.Children.Add(infoBar);

// Remove from visual tree when closed
void OnClosed(object? s, InfoBarClosedEventArgs args)
{
infoBar.Closed -= OnClosed;
if (GridRoot.Children.Contains(infoBar))
{
GridRoot.Children.Remove(infoBar);
}
}

infoBar.Closed += OnClosed;
});
}

private async Task ShowErrorInfoBarAsync(string message)
{
await ShowInfoBarAsync("Service Action Result", $"Error: {message}", InfoBarSeverity.Error);
}
}
}
Loading
Loading