Add Windows SSH monitoring MVP #1
3 changed files with 110 additions and 2 deletions
|
|
@ -18,8 +18,8 @@
|
||||||
- [x] сохранять профили локально без паролей;
|
- [x] сохранять профили локально без паролей;
|
||||||
- [x] получать CPU/load, RAM, disk, uptime и latency;
|
- [x] получать CPU/load, RAM, disk, uptime и latency;
|
||||||
- [x] собрать и реально запустить x64-приложение;
|
- [x] собрать и реально запустить x64-приложение;
|
||||||
- [ ] редактирование и удаление серверов;
|
- [x] редактирование и удаление серверов;
|
||||||
- [ ] единственный изменяемый Hub;
|
- [x] единственный изменяемый Hub;
|
||||||
- [ ] настоящие страницы Servers, Links, Sessions и Settings.
|
- [ ] настоящие страницы Servers, Links, Sessions и Settings.
|
||||||
|
|
||||||
## Этап 2 — установщик Hub/Node
|
## Этап 2 — установщик Hub/Node
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,16 @@
|
||||||
Click="AddServerButton_Click"
|
Click="AddServerButton_Click"
|
||||||
Icon="Add"
|
Icon="Add"
|
||||||
Label="Добавить сервер" />
|
Label="Добавить сервер" />
|
||||||
|
<AppBarButton
|
||||||
|
AutomationProperties.Name="Изменить выбранный сервер"
|
||||||
|
Click="EditServerButton_Click"
|
||||||
|
Icon="Edit"
|
||||||
|
Label="Изменить" />
|
||||||
|
<AppBarButton
|
||||||
|
AutomationProperties.Name="Удалить выбранный сервер"
|
||||||
|
Click="DeleteServerButton_Click"
|
||||||
|
Icon="Delete"
|
||||||
|
Label="Удалить" />
|
||||||
</CommandBar>
|
</CommandBar>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,11 @@ public sealed partial class MainPage : Page
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (hubBox.IsChecked == true && Servers.Any(server => server.IsHub))
|
||||||
|
{
|
||||||
|
ShowInfo("Hub уже выбран", "Измените существующий Hub или снимите эту отметку.", InfoBarSeverity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var profile = new ServerProfileData(
|
var profile = new ServerProfileData(
|
||||||
Guid.NewGuid().ToString("N"),
|
Guid.NewGuid().ToString("N"),
|
||||||
|
|
@ -169,6 +174,99 @@ public sealed partial class MainPage : Page
|
||||||
await RefreshServerAsync(server);
|
await RefreshServerAsync(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void EditServerButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ServerList.SelectedItem is not ServerViewModel selected)
|
||||||
|
{
|
||||||
|
ShowInfo("Сервер не выбран", "Выберите сервер в списке для изменения.", InfoBarSeverity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameBox = new TextBox { Header = "Название", Text = selected.Profile.Name };
|
||||||
|
var hostBox = new TextBox { Header = "IP или домен", Text = selected.Profile.Host };
|
||||||
|
var portBox = new NumberBox
|
||||||
|
{
|
||||||
|
Header = "SSH-порт",
|
||||||
|
Value = selected.Profile.Port,
|
||||||
|
Minimum = 1,
|
||||||
|
Maximum = 65535,
|
||||||
|
SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Compact
|
||||||
|
};
|
||||||
|
var userBox = new TextBox { Header = "Пользователь", Text = selected.Profile.User };
|
||||||
|
var hubBox = new CheckBox { Content = "Это главный Mesh Hub", IsChecked = selected.IsHub };
|
||||||
|
AutomationProperties.SetName(hubBox, "Использовать сервер как главный Mesh Hub");
|
||||||
|
var dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
XamlRoot = XamlRoot,
|
||||||
|
Title = "Изменить сервер",
|
||||||
|
Content = new StackPanel
|
||||||
|
{
|
||||||
|
Spacing = 12,
|
||||||
|
MinWidth = 420,
|
||||||
|
Children = { nameBox, hostBox, portBox, userBox, hubBox }
|
||||||
|
},
|
||||||
|
PrimaryButtonText = "Сохранить",
|
||||||
|
CloseButtonText = "Отмена",
|
||||||
|
DefaultButton = ContentDialogButton.Primary
|
||||||
|
};
|
||||||
|
if (await dialog.ShowAsync() != ContentDialogResult.Primary)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(nameBox.Text)
|
||||||
|
|| string.IsNullOrWhiteSpace(hostBox.Text)
|
||||||
|
|| string.IsNullOrWhiteSpace(userBox.Text)
|
||||||
|
|| double.IsNaN(portBox.Value))
|
||||||
|
{
|
||||||
|
ShowInfo("Данные не сохранены", "Название, адрес, порт и пользователь обязательны.", InfoBarSeverity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hubBox.IsChecked == true && Servers.Any(server => server.IsHub && server != selected))
|
||||||
|
{
|
||||||
|
ShowInfo("Hub уже выбран", "В конфигурации может быть только один главный Mesh Hub.", InfoBarSeverity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = Servers.IndexOf(selected);
|
||||||
|
var updated = new ServerViewModel(new ServerProfileData(
|
||||||
|
selected.Profile.Id,
|
||||||
|
nameBox.Text.Trim(),
|
||||||
|
hostBox.Text.Trim(),
|
||||||
|
checked((int)portBox.Value),
|
||||||
|
userBox.Text.Trim(),
|
||||||
|
hubBox.IsChecked == true));
|
||||||
|
Servers[index] = updated;
|
||||||
|
await SaveProfilesAsync();
|
||||||
|
await RefreshServerAsync(updated);
|
||||||
|
ShowInfo("Сервер изменён", updated.Name, InfoBarSeverity.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void DeleteServerButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ServerList.SelectedItem is not ServerViewModel selected)
|
||||||
|
{
|
||||||
|
ShowInfo("Сервер не выбран", "Выберите сервер в списке для удаления.", InfoBarSeverity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
XamlRoot = XamlRoot,
|
||||||
|
Title = $"Удалить {selected.Name}?",
|
||||||
|
Content = "Удаляется только локальный профиль. Серверная часть и WireGuard Node останутся установленными.",
|
||||||
|
PrimaryButtonText = "Удалить профиль",
|
||||||
|
CloseButtonText = "Отмена",
|
||||||
|
DefaultButton = ContentDialogButton.Close
|
||||||
|
};
|
||||||
|
if (await dialog.ShowAsync() != ContentDialogResult.Primary)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Servers.Remove(selected);
|
||||||
|
await SaveProfilesAsync();
|
||||||
|
UpdateEmptyState();
|
||||||
|
ShowInfo("Профиль удалён", selected.Name, InfoBarSeverity.Success);
|
||||||
|
}
|
||||||
|
|
||||||
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||||
=> await RefreshAllAsync();
|
=> await RefreshAllAsync();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue