begin application build overhaul
This commit is contained in:
53
Views/MainWindow.axaml
Normal file
53
Views/MainWindow.axaml
Normal file
@@ -0,0 +1,53 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="UnifiedFarmLauncher.Views.MainWindow"
|
||||
Title="Unified Farm Launcher"
|
||||
Width="1000" Height="700"
|
||||
MinWidth="800" MinHeight="600">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Toolbar -->
|
||||
<StackPanel Orientation="Horizontal" Margin="5" Grid.Row="0">
|
||||
<Button Name="AddWorkerButton" Content="Add Worker" Margin="5" Width="120"/>
|
||||
<Button Name="EditWorkerButton" Content="Edit Worker" Margin="5" Width="120"/>
|
||||
<Button Name="DeleteWorkerButton" Content="Delete Worker" Margin="5" Width="120"/>
|
||||
<Separator Margin="10,0"/>
|
||||
<Button Name="StartWorkerButton" Content="Start" Margin="5" Width="80"/>
|
||||
<Button Name="StopWorkerButton" Content="Stop" Margin="5" Width="80"/>
|
||||
<Button Name="AttachWorkerButton" Content="Attach" Margin="5" Width="80"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Worker Type Filter -->
|
||||
<TabControl Name="WorkerTypeTabs" Grid.Row="1" Margin="5,0">
|
||||
<TabItem Header="All Workers">
|
||||
<TextBlock Text="All Workers" IsVisible="False"/>
|
||||
</TabItem>
|
||||
<TabItem Header="SheepIt">
|
||||
<TextBlock Text="SheepIt" IsVisible="False"/>
|
||||
</TabItem>
|
||||
<TabItem Header="Flamenco">
|
||||
<TextBlock Text="Flamenco" IsVisible="False"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<!-- Worker List -->
|
||||
<DataGrid Name="WorkersGrid" Grid.Row="2" Margin="5"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
SelectionMode="Single"
|
||||
GridLinesVisibility="All">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="150"/>
|
||||
<DataGridCheckBoxColumn Header="Enabled" Binding="{Binding Enabled}" Width="80"/>
|
||||
<DataGridTextColumn Header="SSH Host" Binding="{Binding Ssh.Host}" Width="150"/>
|
||||
<DataGridTextColumn Header="SSH Port" Binding="{Binding Ssh.Port}" Width="80"/>
|
||||
<DataGridTextColumn Header="Worker Types" Binding="{Binding WorkerTypes}" Width="*"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<StatusBar Grid.Row="3">
|
||||
<TextBlock Name="StatusText" Text="{Binding StatusText}" Margin="5"/>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
211
Views/MainWindow.axaml.cs
Normal file
211
Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using UnifiedFarmLauncher.Models;
|
||||
using UnifiedFarmLauncher.Services;
|
||||
using UnifiedFarmLauncher.ViewModels;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
|
||||
namespace UnifiedFarmLauncher.Views
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly ConfigService _configService = new();
|
||||
private readonly SshService _sshService = new();
|
||||
private readonly WorkerControllerService _controllerService;
|
||||
private readonly AttachService _attachService;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_controllerService = new WorkerControllerService(_sshService, _configService);
|
||||
_attachService = new AttachService(_sshService, _controllerService);
|
||||
DataContext = new MainWindowViewModel();
|
||||
SetupEventHandlers();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void SetupEventHandlers()
|
||||
{
|
||||
AddWorkerButton.Click += AddWorkerButton_Click;
|
||||
EditWorkerButton.Click += EditWorkerButton_Click;
|
||||
DeleteWorkerButton.Click += DeleteWorkerButton_Click;
|
||||
StartWorkerButton.Click += StartWorkerButton_Click;
|
||||
StopWorkerButton.Click += StopWorkerButton_Click;
|
||||
AttachWorkerButton.Click += AttachWorkerButton_Click;
|
||||
WorkerTypeTabs.SelectionChanged += WorkerTypeTabs_SelectionChanged;
|
||||
WorkersGrid.SelectionChanged += WorkersGrid_SelectionChanged;
|
||||
}
|
||||
|
||||
private async void AddWorkerButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new WorkerEditWindow();
|
||||
if (await dialog.ShowDialogAsync(this))
|
||||
{
|
||||
((MainWindowViewModel)DataContext!).RefreshWorkers();
|
||||
}
|
||||
}
|
||||
|
||||
private async void EditWorkerButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WorkersGrid.SelectedItem is WorkerConfig worker)
|
||||
{
|
||||
var dialog = new WorkerEditWindow(worker);
|
||||
if (await dialog.ShowDialogAsync(this))
|
||||
{
|
||||
((MainWindowViewModel)DataContext!).RefreshWorkers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteWorkerButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WorkersGrid.SelectedItem is WorkerConfig worker)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard("Delete Worker",
|
||||
$"Are you sure you want to delete worker '{worker.Name}'?",
|
||||
ButtonEnum.YesNo, Icon.Warning);
|
||||
var result = await box.ShowAsync();
|
||||
|
||||
if (result == ButtonResult.Yes)
|
||||
{
|
||||
_configService.DeleteWorker(worker.Id);
|
||||
((MainWindowViewModel)DataContext!).RefreshWorkers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void StartWorkerButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WorkersGrid.SelectedItem is WorkerConfig worker)
|
||||
{
|
||||
try
|
||||
{
|
||||
string? workerType = null;
|
||||
if (worker.WorkerTypes.SheepIt != null)
|
||||
workerType = "sheepit";
|
||||
else if (worker.WorkerTypes.Flamenco != null)
|
||||
workerType = "flamenco";
|
||||
|
||||
if (workerType == null)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
"Worker has no configured worker type.",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await box.ShowAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _controllerService.StartWorkerAsync(worker, workerType);
|
||||
var successBox = MessageBoxManager.GetMessageBoxStandard("Start Worker",
|
||||
$"Worker '{worker.Name}' started successfully.",
|
||||
ButtonEnum.Ok, Icon.Success);
|
||||
await successBox.ShowAsync();
|
||||
((MainWindowViewModel)DataContext!).RefreshWorkers();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
var errorBox = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
$"Failed to start worker: {ex.Message}",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await errorBox.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void StopWorkerButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WorkersGrid.SelectedItem is WorkerConfig worker)
|
||||
{
|
||||
try
|
||||
{
|
||||
string? workerType = null;
|
||||
if (worker.WorkerTypes.SheepIt != null)
|
||||
workerType = "sheepit";
|
||||
else if (worker.WorkerTypes.Flamenco != null)
|
||||
workerType = "flamenco";
|
||||
|
||||
if (workerType == null)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
"Worker has no configured worker type.",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await box.ShowAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _controllerService.StopWorkerAsync(worker, workerType);
|
||||
var successBox = MessageBoxManager.GetMessageBoxStandard("Stop Worker",
|
||||
$"Stop command sent to worker '{worker.Name}'.",
|
||||
ButtonEnum.Ok, Icon.Info);
|
||||
await successBox.ShowAsync();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
var errorBox = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
$"Failed to stop worker: {ex.Message}",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await errorBox.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void AttachWorkerButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (WorkersGrid.SelectedItem is WorkerConfig worker)
|
||||
{
|
||||
try
|
||||
{
|
||||
string? workerType = null;
|
||||
if (worker.WorkerTypes.SheepIt != null)
|
||||
workerType = "sheepit";
|
||||
else if (worker.WorkerTypes.Flamenco != null)
|
||||
workerType = "flamenco";
|
||||
|
||||
if (workerType == null)
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
"Worker has no configured worker type.",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await box.ShowAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await _attachService.AttachToWorkerAsync(worker, workerType);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
var errorBox = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
$"Failed to attach to worker: {ex.Message}",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await errorBox.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WorkerTypeTabs_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (WorkerTypeTabs.SelectedItem is TabItem tab)
|
||||
{
|
||||
var type = tab.Header?.ToString() ?? "All";
|
||||
if (type == "All Workers") type = "All";
|
||||
((MainWindowViewModel)DataContext!).SelectedWorkerType = type;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorkersGrid_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is MainWindowViewModel vm)
|
||||
{
|
||||
vm.SelectedWorker = WorkersGrid.SelectedItem as WorkerConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
Views/WorkerEditWindow.axaml
Normal file
90
Views/WorkerEditWindow.axaml
Normal file
@@ -0,0 +1,90 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="UnifiedFarmLauncher.Views.WorkerEditWindow"
|
||||
Title="Edit Worker"
|
||||
Width="600" Height="700"
|
||||
MinWidth="500" MinHeight="600">
|
||||
<Grid RowDefinitions="Auto,*,Auto" Margin="10">
|
||||
<!-- Tabs -->
|
||||
<TabControl Grid.Row="1" Margin="0,10">
|
||||
<!-- Basic Info Tab -->
|
||||
<TabItem Header="Basic Info">
|
||||
<StackPanel Margin="10" Spacing="10">
|
||||
<TextBlock Text="Worker Name:"/>
|
||||
<TextBox Name="NameTextBox" Text="{Binding Name}"/>
|
||||
|
||||
<CheckBox Name="EnabledCheckBox" Content="Enabled" IsChecked="{Binding Enabled}" Margin="0,10,0,0"/>
|
||||
|
||||
<TextBlock Text="SSH Host:" Margin="0,10,0,0"/>
|
||||
<TextBox Name="SshHostTextBox" Text="{Binding SshHost}"/>
|
||||
|
||||
<TextBlock Text="SSH Port:" Margin="0,10,0,0"/>
|
||||
<NumericUpDown Name="SshPortNumeric" Value="{Binding SshPort}" Minimum="1" Maximum="65535"/>
|
||||
|
||||
<TextBlock Text="SSH Args:" Margin="0,10,0,0"/>
|
||||
<TextBox Name="SshArgsTextBox" Text="{Binding SshArgs}"/>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
|
||||
<!-- SheepIt Tab -->
|
||||
<TabItem Header="SheepIt">
|
||||
<StackPanel Margin="10" Spacing="10">
|
||||
<CheckBox Name="HasSheepItCheckBox" Content="Enable SheepIt Worker" IsChecked="{Binding HasSheepIt}" Margin="0,0,0,10"/>
|
||||
|
||||
<TextBlock Text="GPU:" IsVisible="{Binding HasSheepIt}"/>
|
||||
<ComboBox Name="GpuComboBox" IsVisible="{Binding HasSheepIt}" SelectedItem="{Binding SheepItGpu}">
|
||||
<ComboBox.Items>
|
||||
<ComboBoxItem Content="OPTIX_0"/>
|
||||
<ComboBoxItem Content="CUDA_0"/>
|
||||
<ComboBoxItem Content="OPENCL_0"/>
|
||||
</ComboBox.Items>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Text="Username:" IsVisible="{Binding HasSheepIt}" Margin="0,10,0,0"/>
|
||||
<TextBox Name="SheepItUsernameTextBox" Text="{Binding SheepItUsername}" IsVisible="{Binding HasSheepIt}"/>
|
||||
|
||||
<TextBlock Text="Render Key:" IsVisible="{Binding HasSheepIt}" Margin="0,10,0,0"/>
|
||||
<TextBox Name="SheepItRenderKeyTextBox" Text="{Binding SheepItRenderKey}" IsVisible="{Binding HasSheepIt}" PasswordChar="*"/>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
|
||||
<!-- Flamenco Tab -->
|
||||
<TabItem Header="Flamenco">
|
||||
<StackPanel Margin="10" Spacing="10">
|
||||
<CheckBox Name="HasFlamencoCheckBox" Content="Enable Flamenco Worker" IsChecked="{Binding HasFlamenco}" Margin="0,0,0,10"/>
|
||||
|
||||
<TextBlock Text="Worker Path:" IsVisible="{Binding HasFlamenco}"/>
|
||||
<Grid IsVisible="{Binding HasFlamenco}" ColumnDefinitions="*,Auto">
|
||||
<TextBox Name="FlamencoPathTextBox" Grid.Column="0" Text="{Binding FlamencoWorkerPath}" Margin="0,0,5,0"/>
|
||||
<Button Name="BrowseFlamencoPathButton" Grid.Column="1" Content="Browse..." Width="80"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Network Drives:" IsVisible="{Binding HasFlamenco}" Margin="0,10,0,0"/>
|
||||
<Grid IsVisible="{Binding HasFlamenco}" RowDefinitions="*,Auto" Margin="0,5,0,0">
|
||||
<ListBox Name="NetworkDrivesListBox" Grid.Row="0" ItemsSource="{Binding NetworkDrives}" MaxHeight="100"/>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,5,0,0">
|
||||
<Button Name="AddDriveButton" Content="Add" Width="60" Margin="0,0,5,0"/>
|
||||
<Button Name="RemoveDriveButton" Content="Remove" Width="60"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Network Paths:" IsVisible="{Binding HasFlamenco}" Margin="0,10,0,0"/>
|
||||
<Grid IsVisible="{Binding HasFlamenco}" RowDefinitions="*,Auto" Margin="0,5,0,0">
|
||||
<ListBox Name="NetworkPathsListBox" Grid.Row="0" ItemsSource="{Binding NetworkPaths}" MaxHeight="100"/>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,5,0,0">
|
||||
<Button Name="AddPathButton" Content="Add" Width="60" Margin="0,0,5,0"/>
|
||||
<Button Name="RemovePathButton" Content="Remove" Width="60"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2" Spacing="10" Margin="0,10,0,0">
|
||||
<Button Name="OkButton" Content="OK" Width="80" IsDefault="True"/>
|
||||
<Button Name="CancelButton" Content="Cancel" Width="80" IsCancel="True"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
131
Views/WorkerEditWindow.axaml.cs
Normal file
131
Views/WorkerEditWindow.axaml.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using UnifiedFarmLauncher.Models;
|
||||
using UnifiedFarmLauncher.Services;
|
||||
using UnifiedFarmLauncher.ViewModels;
|
||||
using MsBox.Avalonia;
|
||||
using MsBox.Avalonia.Enums;
|
||||
|
||||
namespace UnifiedFarmLauncher.Views
|
||||
{
|
||||
public partial class WorkerEditWindow : Window
|
||||
{
|
||||
private readonly WorkerEditViewModel _viewModel;
|
||||
private bool _result;
|
||||
|
||||
public WorkerEditWindow(WorkerConfig? worker = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
var configService = new ConfigService();
|
||||
_viewModel = new WorkerEditViewModel(configService, worker);
|
||||
DataContext = _viewModel;
|
||||
SetupEventHandlers();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void SetupEventHandlers()
|
||||
{
|
||||
OkButton.Click += OkButton_Click;
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
BrowseFlamencoPathButton.Click += BrowseFlamencoPathButton_Click;
|
||||
AddDriveButton.Click += AddDriveButton_Click;
|
||||
RemoveDriveButton.Click += RemoveDriveButton_Click;
|
||||
AddPathButton.Click += AddPathButton_Click;
|
||||
RemovePathButton.Click += RemovePathButton_Click;
|
||||
}
|
||||
|
||||
private async void OkButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_viewModel.Name))
|
||||
{
|
||||
var box = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
"Worker name is required.",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await box.ShowAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_viewModel.Save();
|
||||
_result = true;
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorBox = MessageBoxManager.GetMessageBoxStandard("Error",
|
||||
$"Failed to save worker: {ex.Message}",
|
||||
ButtonEnum.Ok, Icon.Error);
|
||||
await errorBox.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_result = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private async void BrowseFlamencoPathButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
if (topLevel?.StorageProvider.CanPickFolder == true)
|
||||
{
|
||||
var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||
{
|
||||
Title = "Select Flamenco Worker Path"
|
||||
});
|
||||
|
||||
if (folders.Count > 0 && folders[0].TryGetLocalPath() is { } localPath)
|
||||
{
|
||||
_viewModel.FlamencoWorkerPath = localPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddDriveButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
// Simplified: use a simple input box
|
||||
// In a full implementation, you'd use a proper input dialog
|
||||
_viewModel.NetworkDrives.Add("A:");
|
||||
}
|
||||
|
||||
private void RemoveDriveButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (NetworkDrivesListBox.SelectedItem is string drive)
|
||||
{
|
||||
_viewModel.NetworkDrives.Remove(drive);
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddPathButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
// Simplified: use a simple input box
|
||||
// In a full implementation, you'd use a proper input dialog
|
||||
_viewModel.NetworkPaths.Add("\\\\SERVER\\share");
|
||||
}
|
||||
|
||||
private void RemovePathButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (NetworkPathsListBox.SelectedItem is string path)
|
||||
{
|
||||
_viewModel.NetworkPaths.Remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ShowDialogAsync(Window parent)
|
||||
{
|
||||
await base.ShowDialog(parent);
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user