Add dedicated desktop management pages
This commit is contained in:
parent
199e70c804
commit
a8978c085e
13 changed files with 533 additions and 54 deletions
|
|
@ -122,7 +122,7 @@ In the application, generate or copy the monitoring SSH key, add the Hub profile
|
|||
|
||||
`v0.1.0-alpha.4` is an early testing release, not a production security appliance. Windows and Linux builds, control-plane tests, Bash syntax checks, self-contained `linux-x64`/`linux-arm64` artifacts, and checksums are automated in GitHub Actions.
|
||||
|
||||
The current development branch implements Windows SSH monitoring, the Hub/Node WireGuard installer, directional Links, one-time enrollment, separate mTLS Agent, Operator, and source-scoped Automation identities, certificate revocation/re-enrollment, SQLite control state, audit, authenticated event streaming, Windows Control API integration, and a bounded durable Agent buffer with downsampling.
|
||||
The current development branch implements dedicated Windows pages for Servers, Links, Sessions, and Settings; SSH monitoring; the Hub/Node WireGuard installer; directional Links; one-time enrollment; separate mTLS Agent, Operator, and source-scoped Automation identities; certificate revocation/re-enrollment; SQLite control state; audit; authenticated event streaming; Windows Control API integration; and a bounded durable Agent buffer with downsampling.
|
||||
|
||||
Reconnect reconciliation is implemented with a durable SQLite marker: after a Node returns, the Hub reapplies the latest effective disabled policies and clears the marker only after the firewall confirms success. Linux CI exercises the real Control-to-helper process boundary, including a helper failure and Control process reconstruction over the same SQLite database. CI also runs a 100-Node concurrent heartbeat and replay scenario against one Hub store. Still planned: end-to-end nftables and host-reboot tests with the installer, a signed Windows installer, and desktop/mobile clients for additional platforms.
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
- [x] собрать и реально запустить x64-приложение;
|
||||
- [x] редактирование и удаление серверов;
|
||||
- [x] единственный изменяемый Hub;
|
||||
- [ ] настоящие страницы Servers, Links, Sessions и Settings.
|
||||
- [x] настоящие страницы Servers, Links, Sessions и Settings.
|
||||
|
||||
## Этап 2 — установщик Hub/Node
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
Text="SSH monitoring · серверы ещё не добавлены" />
|
||||
</StackPanel>
|
||||
<CommandBar
|
||||
x:Name="OverviewCommandBar"
|
||||
Grid.Column="1"
|
||||
Background="Transparent"
|
||||
DefaultLabelPosition="Right">
|
||||
|
|
@ -425,22 +426,11 @@
|
|||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<StackPanel
|
||||
x:Name="NavigationPlaceholder"
|
||||
<ContentPresenter
|
||||
x:Name="NavigationPageHost"
|
||||
Grid.Row="3"
|
||||
Margin="0,36,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
MaxWidth="560"
|
||||
Spacing="12"
|
||||
Visibility="Collapsed">
|
||||
<TextBlock x:Name="PlaceholderTitle" HorizontalAlignment="Center" Style="{StaticResource TitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
x:Name="PlaceholderDescription"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
Margin="0,20,0,0"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
</Page>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ public sealed partial class MainPage : Page
|
|||
private readonly DispatcherTimer _refreshTimer = new() { Interval = TimeSpan.FromSeconds(30) };
|
||||
private readonly SemaphoreSlim _refreshLock = new(1, 1);
|
||||
private readonly CancellationTokenSource _controlCancellation = new();
|
||||
private ServersPage? _serversPage;
|
||||
private LinksPage? _linksPage;
|
||||
private SessionsPage? _sessionsPage;
|
||||
private SettingsPage? _settingsPage;
|
||||
private bool _loaded;
|
||||
private bool _controlListening;
|
||||
|
||||
|
|
@ -38,6 +42,62 @@ public sealed partial class MainPage : Page
|
|||
public ObservableCollection<MeshNodeViewModel> MeshNodes { get; } = [];
|
||||
public ObservableCollection<MeshLinkViewModel> MeshLinks { get; } = [];
|
||||
|
||||
internal bool IsControlConfigured => _control.IsConfigured;
|
||||
|
||||
internal void AddServerFromPage() => AddServerButton_Click(this, new RoutedEventArgs());
|
||||
|
||||
internal void EditServerFromPage(ServerViewModel? server)
|
||||
{
|
||||
ServerList.SelectedItem = server;
|
||||
EditServerButton_Click(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
internal void DeleteServerFromPage(ServerViewModel? server)
|
||||
{
|
||||
ServerList.SelectedItem = server;
|
||||
DeleteServerButton_Click(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
internal void OpenTerminalFromPage(ServerViewModel? server)
|
||||
{
|
||||
ServerList.SelectedItem = server;
|
||||
TerminalButton_Click(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
internal async Task RefreshServersFromPageAsync()
|
||||
=> await RefreshAllAsync();
|
||||
|
||||
internal async Task RefreshLinksFromPageAsync()
|
||||
=> await RefreshMeshAsync();
|
||||
|
||||
internal async Task ChangeLinkFromPageAsync(
|
||||
MeshNodeViewModel? source,
|
||||
MeshNodeViewModel? target,
|
||||
string protocol,
|
||||
int port,
|
||||
int ttlMinutes,
|
||||
bool enable)
|
||||
{
|
||||
SourceNodeBox.SelectedItem = source;
|
||||
TargetNodeBox.SelectedItem = target;
|
||||
LinkProtocolBox.SelectedIndex = string.Equals(protocol, "udp", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
||||
LinkPortBox.Value = port;
|
||||
LinkTtlBox.Value = ttlMinutes;
|
||||
await ChangeLinkAsync(enable);
|
||||
}
|
||||
|
||||
internal void ReenrollNodeFromPage(MeshNodeViewModel? source)
|
||||
{
|
||||
SourceNodeBox.SelectedItem = source;
|
||||
ReenrollNodeButton_Click(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
internal void ShowSshKeyFromPage() => SshKeyButton_Click(this, new RoutedEventArgs());
|
||||
|
||||
internal void ConnectControlHubFromPage() => ControlHubButton_Click(this, new RoutedEventArgs());
|
||||
|
||||
internal void ExportDiagnosticsFromPage() => ExportDiagnosticsButton_Click(this, new RoutedEventArgs());
|
||||
|
||||
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_loaded)
|
||||
|
|
@ -1083,57 +1143,62 @@ public sealed partial class MainPage : Page
|
|||
{
|
||||
if (args.IsSettingsSelected)
|
||||
{
|
||||
ShowPlaceholder(
|
||||
"Настройки",
|
||||
"Профили серверов хранятся локально. Настройки ключей, интервалов обновления и подтверждений будут добавляться здесь.");
|
||||
_settingsPage ??= new SettingsPage(this);
|
||||
ShowNavigationPage(_settingsPage);
|
||||
return;
|
||||
}
|
||||
|
||||
var tag = (args.SelectedItem as NavigationViewItem)?.Tag?.ToString() ?? "overview";
|
||||
NavigationPlaceholder.Visibility = Visibility.Collapsed;
|
||||
switch (tag)
|
||||
{
|
||||
case "servers":
|
||||
_serversPage ??= new ServersPage(this);
|
||||
ShowNavigationPage(_serversPage);
|
||||
break;
|
||||
case "links":
|
||||
_linksPage ??= new LinksPage(this);
|
||||
ShowNavigationPage(_linksPage);
|
||||
break;
|
||||
case "sessions":
|
||||
_sessionsPage ??= new SessionsPage(this);
|
||||
ShowNavigationPage(_sessionsPage);
|
||||
break;
|
||||
default:
|
||||
ShowOverview();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowOverview()
|
||||
{
|
||||
NavigationPageHost.Visibility = Visibility.Collapsed;
|
||||
NavigationPageHost.Content = null;
|
||||
OverviewCommandBar.Visibility = Visibility.Visible;
|
||||
OverviewSummary.Visibility = Visibility.Visible;
|
||||
WorkspaceScroll.Visibility = Visibility.Visible;
|
||||
ServerWorkspace.Visibility = tag is "overview" or "servers" ? Visibility.Visible : Visibility.Collapsed;
|
||||
LinkInspector.Visibility = tag is "overview" or "links" ? Visibility.Visible : Visibility.Collapsed;
|
||||
InspectorColumn.Width = tag == "overview" && ActualWidth >= 1080
|
||||
? new GridLength(360)
|
||||
: new GridLength(0);
|
||||
|
||||
if (tag == "links")
|
||||
ServerWorkspace.Visibility = Visibility.Visible;
|
||||
LinkInspector.Visibility = Visibility.Visible;
|
||||
Grid.SetColumnSpan(LinkInspector, 1);
|
||||
if (ActualWidth >= 1080)
|
||||
{
|
||||
Grid.SetRow(LinkInspector, 0);
|
||||
Grid.SetColumn(LinkInspector, 0);
|
||||
Grid.SetColumnSpan(LinkInspector, 2);
|
||||
Grid.SetColumn(LinkInspector, 1);
|
||||
InspectorColumn.Width = new GridLength(360);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grid.SetColumnSpan(LinkInspector, 1);
|
||||
if (ActualWidth >= 1080)
|
||||
{
|
||||
Grid.SetRow(LinkInspector, 0);
|
||||
Grid.SetColumn(LinkInspector, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grid.SetRow(LinkInspector, 1);
|
||||
Grid.SetColumn(LinkInspector, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (tag == "sessions")
|
||||
{
|
||||
ShowPlaceholder(
|
||||
"SSH-сессии",
|
||||
"Интерактивные терминалы будут использовать отдельную identity и не получат ключ мониторинга или права Mesh Hub.");
|
||||
Grid.SetRow(LinkInspector, 1);
|
||||
Grid.SetColumn(LinkInspector, 0);
|
||||
InspectorColumn.Width = new GridLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowPlaceholder(string title, string description)
|
||||
private void ShowNavigationPage(Page page)
|
||||
{
|
||||
OverviewCommandBar.Visibility = Visibility.Collapsed;
|
||||
OverviewSummary.Visibility = Visibility.Collapsed;
|
||||
WorkspaceScroll.Visibility = Visibility.Collapsed;
|
||||
PlaceholderTitle.Text = title;
|
||||
PlaceholderDescription.Text = description;
|
||||
NavigationPlaceholder.Visibility = Visibility.Visible;
|
||||
NavigationPageHost.Content = page;
|
||||
NavigationPageHost.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
104
src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml
Normal file
104
src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="ServerMonitorManager_Desktop.LinksPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:ServerMonitorManager_Desktop">
|
||||
|
||||
<Grid RowSpacing="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="Связи серверов" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Направленные разрешения source → destination с ручным kill switch" />
|
||||
</StackPanel>
|
||||
<CommandBar Grid.Column="1" Background="Transparent" DefaultLabelPosition="Right">
|
||||
<AppBarButton AutomationProperties.Name="Обновить узлы и связи" Click="RefreshButton_Click" Icon="Refresh" Label="Обновить" />
|
||||
<AppBarButton AutomationProperties.Name="Перерегистрировать выбранный исходный Node" Click="ReenrollButton_Click" Label="Перерегистрировать">
|
||||
<AppBarButton.Icon><FontIcon Glyph="" /></AppBarButton.Icon>
|
||||
</AppBarButton>
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
|
||||
<Grid x:Name="LinksWorkspace" Grid.Row="1" ColumnSpacing="20" RowSpacing="20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="EditorColumn" Width="*" />
|
||||
<ColumnDefinition x:Name="ListColumn" Width="0" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border
|
||||
x:Name="LinkEditor"
|
||||
Padding="18"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8">
|
||||
<StackPanel Spacing="14">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Управление доступом" />
|
||||
<ComboBox x:Name="SourceNodeBox" AutomationProperties.Name="Источник связи" DisplayMemberPath="Label" Header="Источник доступа" ItemsSource="{x:Bind Nodes}" />
|
||||
<ComboBox x:Name="TargetNodeBox" AutomationProperties.Name="Цель связи" DisplayMemberPath="Label" Header="Целевой сервер" ItemsSource="{x:Bind Nodes}" />
|
||||
<Grid ColumnSpacing="10">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions>
|
||||
<ComboBox x:Name="ProtocolBox" Header="Протокол" SelectedIndex="0">
|
||||
<ComboBoxItem Content="TCP" Tag="tcp" />
|
||||
<ComboBoxItem Content="UDP" Tag="udp" />
|
||||
</ComboBox>
|
||||
<NumberBox x:Name="PortBox" Grid.Column="1" Header="Порт" Maximum="65535" Minimum="1" SpinButtonPlacementMode="Compact" Value="22" />
|
||||
</Grid>
|
||||
<NumberBox x:Name="TtlBox" Header="TTL, минут (0 — вручную)" Maximum="525600" Minimum="0" SpinButtonPlacementMode="Compact" Value="120" />
|
||||
<Grid ColumnSpacing="10">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions>
|
||||
<Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Click="ConnectButton_Click" Content="Разрешить" Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Click="DisconnectButton_Click" Content="Отключить" />
|
||||
</Grid>
|
||||
<InfoBar IsClosable="False" IsOpen="True" Message="Обратное направление не включается автоматически. Отключение Link удаляет разрешение nftables на Hub." Severity="Informational" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Grid x:Name="LinksListPanel" Grid.Row="1" RowSpacing="10">
|
||||
<Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="*" /></Grid.RowDefinitions>
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Разрешённые направления" />
|
||||
<ListView x:Name="LinksList" Grid.Row="1" AutomationProperties.Name="Список связей" ItemsSource="{x:Bind Links}" SelectionMode="Single">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:MeshLinkViewModel">
|
||||
<Grid MinHeight="62" ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Label}" TextWrapping="Wrap" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Направленная политика доступа" />
|
||||
</StackPanel>
|
||||
<FontIcon Grid.Column="1" VerticalAlignment="Center" Glyph="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="NarrowState" />
|
||||
<VisualState x:Name="WideState">
|
||||
<VisualState.StateTriggers><AdaptiveTrigger MinWindowWidth="820" /></VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="EditorColumn.Width" Value="360" />
|
||||
<Setter Target="ListColumn.Width" Value="*" />
|
||||
<Setter Target="LinksListPanel.(Grid.Row)" Value="0" />
|
||||
<Setter Target="LinksListPanel.(Grid.Column)" Value="1" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
44
src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml.cs
Normal file
44
src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace ServerMonitorManager_Desktop;
|
||||
|
||||
public sealed partial class LinksPage : Page
|
||||
{
|
||||
private readonly MainPage _host;
|
||||
|
||||
internal LinksPage(MainPage host)
|
||||
{
|
||||
_host = host;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ObservableCollection<MeshNodeViewModel> Nodes => _host.MeshNodes;
|
||||
|
||||
public ObservableCollection<MeshLinkViewModel> Links => _host.MeshLinks;
|
||||
|
||||
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||
=> await _host.RefreshLinksFromPageAsync();
|
||||
|
||||
private async void ConnectButton_Click(object sender, RoutedEventArgs e)
|
||||
=> await ChangeLinkAsync(enable: true);
|
||||
|
||||
private async void DisconnectButton_Click(object sender, RoutedEventArgs e)
|
||||
=> await ChangeLinkAsync(enable: false);
|
||||
|
||||
private void ReenrollButton_Click(object sender, RoutedEventArgs e)
|
||||
=> _host.ReenrollNodeFromPage(SourceNodeBox.SelectedItem as MeshNodeViewModel);
|
||||
|
||||
private async Task ChangeLinkAsync(bool enable)
|
||||
{
|
||||
var protocol = (ProtocolBox.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "tcp";
|
||||
await _host.ChangeLinkFromPageAsync(
|
||||
SourceNodeBox.SelectedItem as MeshNodeViewModel,
|
||||
TargetNodeBox.SelectedItem as MeshNodeViewModel,
|
||||
protocol,
|
||||
double.IsNaN(PortBox.Value) ? 0 : checked((int)PortBox.Value),
|
||||
double.IsNaN(TtlBox.Value) ? 0 : checked((int)TtlBox.Value),
|
||||
enable);
|
||||
}
|
||||
}
|
||||
75
src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml
Normal file
75
src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="ServerMonitorManager_Desktop.ServersPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:ServerMonitorManager_Desktop">
|
||||
|
||||
<Grid RowSpacing="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="Серверы" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Локальные SSH-профили и текущее состояние ресурсов" />
|
||||
</StackPanel>
|
||||
<CommandBar Grid.Column="1" Background="Transparent" DefaultLabelPosition="Right">
|
||||
<AppBarButton AutomationProperties.Name="Обновить все серверы" Click="RefreshButton_Click" Icon="Refresh" Label="Обновить" />
|
||||
<AppBarButton AutomationProperties.Name="Добавить сервер" Click="AddButton_Click" Icon="Add" Label="Добавить" />
|
||||
<AppBarButton AutomationProperties.Name="Изменить выбранный сервер" Click="EditButton_Click" Icon="Edit" Label="Изменить" />
|
||||
<AppBarButton AutomationProperties.Name="Удалить выбранный сервер" Click="DeleteButton_Click" Icon="Delete" Label="Удалить" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
|
||||
<InfoBar
|
||||
x:Name="EmptyServersInfo"
|
||||
Grid.Row="1"
|
||||
IsClosable="False"
|
||||
Message="Добавьте первый сервер. Пароли не сохраняются; мониторинг использует отдельный ограниченный SSH-ключ."
|
||||
Severity="Informational"
|
||||
Title="Список серверов пуст" />
|
||||
|
||||
<ListView
|
||||
x:Name="ServerList"
|
||||
Grid.Row="2"
|
||||
AutomationProperties.Name="Список серверов"
|
||||
ItemsSource="{x:Bind Servers}"
|
||||
SelectionMode="Single">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:ServerViewModel">
|
||||
<Grid MinHeight="88" Padding="4" ColumnSpacing="18">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Endpoint}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Status, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="6">
|
||||
<TextBlock Text="{x:Bind CpuText, Mode=OneWay}" />
|
||||
<ProgressBar Maximum="100" Value="{x:Bind CpuPercent, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" VerticalAlignment="Center" Spacing="2">
|
||||
<TextBlock Text="{x:Bind MemoryText, Mode=OneWay}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind DiskText, Mode=OneWay}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind HealthText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="3" VerticalAlignment="Center" Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind LatencyText, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Page>
|
||||
33
src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml.cs
Normal file
33
src/ServerMonitorManager.Desktop/Pages/ServersPage.xaml.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace ServerMonitorManager_Desktop;
|
||||
|
||||
public sealed partial class ServersPage : Page
|
||||
{
|
||||
private readonly MainPage _host;
|
||||
|
||||
internal ServersPage(MainPage host)
|
||||
{
|
||||
_host = host;
|
||||
InitializeComponent();
|
||||
Servers.CollectionChanged += (_, _) => UpdateEmptyState();
|
||||
Loaded += (_, _) => UpdateEmptyState();
|
||||
}
|
||||
|
||||
public ObservableCollection<ServerViewModel> Servers => _host.Servers;
|
||||
|
||||
private ServerViewModel? SelectedServer => ServerList.SelectedItem as ServerViewModel;
|
||||
|
||||
private void AddButton_Click(object sender, RoutedEventArgs e) => _host.AddServerFromPage();
|
||||
|
||||
private void EditButton_Click(object sender, RoutedEventArgs e) => _host.EditServerFromPage(SelectedServer);
|
||||
|
||||
private void DeleteButton_Click(object sender, RoutedEventArgs e) => _host.DeleteServerFromPage(SelectedServer);
|
||||
|
||||
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||
=> await _host.RefreshServersFromPageAsync();
|
||||
|
||||
private void UpdateEmptyState() => EmptyServersInfo.IsOpen = Servers.Count == 0;
|
||||
}
|
||||
59
src/ServerMonitorManager.Desktop/Pages/SessionsPage.xaml
Normal file
59
src/ServerMonitorManager.Desktop/Pages/SessionsPage.xaml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="ServerMonitorManager_Desktop.SessionsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:ServerMonitorManager_Desktop">
|
||||
|
||||
<Grid RowSpacing="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="SSH-сессии" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Прямые интерактивные подключения через системный OpenSSH" />
|
||||
</StackPanel>
|
||||
<CommandBar Grid.Column="1" Background="Transparent" DefaultLabelPosition="Right">
|
||||
<AppBarButton AutomationProperties.Name="Открыть SSH-терминал для выбранного сервера" Click="OpenTerminalButton_Click" Icon="OpenFile" Label="Открыть терминал" />
|
||||
</CommandBar>
|
||||
</Grid>
|
||||
<InfoBar
|
||||
Grid.Row="1"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="Интерактивный терминал использует ваши обычные SSH-ключи. Ограниченный ключ мониторинга, Operator-сертификат и Automation identity в терминал не передаются."
|
||||
Severity="Informational"
|
||||
Title="Разделение полномочий" />
|
||||
<ListView
|
||||
x:Name="SessionTargetsList"
|
||||
Grid.Row="2"
|
||||
AutomationProperties.Name="Серверы для SSH-сессии"
|
||||
ItemsSource="{x:Bind Servers}"
|
||||
SelectionMode="Single">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:ServerViewModel">
|
||||
<Grid MinHeight="68" ColumnSpacing="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Name}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Endpoint}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{x:Bind Status, Mode=OneWay}" />
|
||||
<FontIcon Grid.Column="2" VerticalAlignment="Center" Glyph="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Page>
|
||||
21
src/ServerMonitorManager.Desktop/Pages/SessionsPage.xaml.cs
Normal file
21
src/ServerMonitorManager.Desktop/Pages/SessionsPage.xaml.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace ServerMonitorManager_Desktop;
|
||||
|
||||
public sealed partial class SessionsPage : Page
|
||||
{
|
||||
private readonly MainPage _host;
|
||||
|
||||
internal SessionsPage(MainPage host)
|
||||
{
|
||||
_host = host;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ObservableCollection<ServerViewModel> Servers => _host.Servers;
|
||||
|
||||
private void OpenTerminalButton_Click(object sender, RoutedEventArgs e)
|
||||
=> _host.OpenTerminalFromPage(SessionTargetsList.SelectedItem as ServerViewModel);
|
||||
}
|
||||
54
src/ServerMonitorManager.Desktop/Pages/SettingsPage.xaml
Normal file
54
src/ServerMonitorManager.Desktop/Pages/SettingsPage.xaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="ServerMonitorManager_Desktop.SettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel MaxWidth="840" HorizontalAlignment="Left" Spacing="20">
|
||||
<StackPanel Spacing="3">
|
||||
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="Настройки" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Подключения, локальные ключи и обслуживание приложения" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid ColumnSpacing="16" RowSpacing="16">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /></Grid.RowDefinitions>
|
||||
|
||||
<Border Padding="18" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="8">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Control Hub" />
|
||||
<TextBlock x:Name="ControlStatusText" Foreground="{ThemeResource TextFillColorSecondaryBrush}" TextWrapping="Wrap" />
|
||||
<Button HorizontalAlignment="Left" Click="ConnectControlButton_Click" Content="Подключить по SMMDEV1" Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1" Padding="18" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="8">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="SSH-ключ мониторинга" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Ed25519 private key защищён Windows DPAPI и не передаётся Hub." TextWrapping="Wrap" />
|
||||
<Button HorizontalAlignment="Left" Click="ShowSshKeyButton_Click" Content="Показать публичный ключ" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Padding="18" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="8">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Мониторинг" />
|
||||
<TextBlock Text="Интервал обновления: 30 секунд" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="История: до 240 точек на сервер. Старые offline-точки уплотняются Linux Agent." TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="1" Padding="18" Background="{ThemeResource CardBackgroundFillColorDefaultBrush}" BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="1" CornerRadius="8">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="Диагностика" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Экспорт не содержит адресов, пользователей, ключей, сертификатов или токенов." TextWrapping="Wrap" />
|
||||
<Button HorizontalAlignment="Left" Click="ExportDiagnosticsButton_Click" Content="Экспортировать JSON" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<InfoBar IsClosable="False" IsOpen="True" Message="Agent, Operator, terminal и Automation используют разные identities. Изменять Links может только Operator." Severity="Informational" Title="Безопасность" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Page>
|
||||
33
src/ServerMonitorManager.Desktop/Pages/SettingsPage.xaml.cs
Normal file
33
src/ServerMonitorManager.Desktop/Pages/SettingsPage.xaml.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace ServerMonitorManager_Desktop;
|
||||
|
||||
public sealed partial class SettingsPage : Page
|
||||
{
|
||||
private readonly MainPage _host;
|
||||
|
||||
internal SettingsPage(MainPage host)
|
||||
{
|
||||
_host = host;
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) => UpdateStatus();
|
||||
}
|
||||
|
||||
private void ConnectControlButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_host.ConnectControlHubFromPage();
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void ShowSshKeyButton_Click(object sender, RoutedEventArgs e)
|
||||
=> _host.ShowSshKeyFromPage();
|
||||
|
||||
private void ExportDiagnosticsButton_Click(object sender, RoutedEventArgs e)
|
||||
=> _host.ExportDiagnosticsFromPage();
|
||||
|
||||
private void UpdateStatus()
|
||||
=> ControlStatusText.Text = _host.IsControlConfigured
|
||||
? "Operator-сертификат установлен и защищён Windows DPAPI."
|
||||
: "Control Hub ещё не подключён. Создайте одноразовый device code на Hub.";
|
||||
}
|
||||
|
|
@ -73,7 +73,8 @@
|
|||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<!-- WinUI pages are activated through generated XAML metadata. Keep trimming disabled
|
||||
until every desktop serialization and activation path has an explicit trim contract. -->
|
||||
<PublishTrimmed>False</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in a new issue