diff --git a/README.md b/README.md index 51c7fd8..f138916 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/roadmap.md b/docs/roadmap.md index fd76c21..f1033c8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -20,7 +20,7 @@ - [x] собрать и реально запустить x64-приложение; - [x] редактирование и удаление серверов; - [x] единственный изменяемый Hub; -- [ ] настоящие страницы Servers, Links, Sessions и Settings. +- [x] настоящие страницы Servers, Links, Sessions и Settings. ## Этап 2 — установщик Hub/Node diff --git a/src/ServerMonitorManager.Desktop/MainPage.xaml b/src/ServerMonitorManager.Desktop/MainPage.xaml index 32def29..a9b2a23 100644 --- a/src/ServerMonitorManager.Desktop/MainPage.xaml +++ b/src/ServerMonitorManager.Desktop/MainPage.xaml @@ -61,6 +61,7 @@ Text="SSH monitoring · серверы ещё не добавлены" /> @@ -425,22 +426,11 @@ - - - - + Margin="0,20,0,0" + Visibility="Collapsed" /> diff --git a/src/ServerMonitorManager.Desktop/MainPage.xaml.cs b/src/ServerMonitorManager.Desktop/MainPage.xaml.cs index cc6f579..5ad5e8e 100644 --- a/src/ServerMonitorManager.Desktop/MainPage.xaml.cs +++ b/src/ServerMonitorManager.Desktop/MainPage.xaml.cs @@ -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 MeshNodes { get; } = []; public ObservableCollection 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; } } diff --git a/src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml b/src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml new file mode 100644 index 0000000..aec83f5 --- /dev/null +++ b/src/ServerMonitorManager.Desktop/Pages/LinksPage.xaml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +