feat: Add main application views and structure
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled

- Implemented CreateInstanceView for creating new instances.
- Added HostsView for managing SSH hosts with CRUD operations.
- Created InstancesView for displaying and managing instances.
- Developed LogsView for viewing operation logs.
- Introduced SecretsView for managing secrets associated with hosts.
- Established SettingsView for configuring application settings.
- Created MainWindow as the main application window with navigation.
- Added app manifest and configuration files for logging and settings.
This commit is contained in:
Matt Batchelder
2026-02-18 10:43:27 -05:00
parent 29b8c23dbb
commit 45c94b6536
149 changed files with 6469 additions and 63498 deletions

View File

@@ -0,0 +1,146 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
x:Class="OTSSignsOrchestrator.Desktop.Views.CreateInstanceView"
x:DataType="vm:CreateInstanceViewModel">
<ScrollViewer>
<Grid ColumnDefinitions="1*,16,1*" Margin="16,12">
<!-- ══ LEFT COLUMN — inputs ══ -->
<StackPanel Grid.Column="0" Spacing="8">
<TextBlock Text="Create New Instance" FontSize="20" FontWeight="Bold" Margin="0,0,0,12" />
<!-- SSH Host -->
<TextBlock Text="Deploy to SSH Host" FontSize="12" />
<ComboBox ItemsSource="{Binding AvailableHosts}"
SelectedItem="{Binding SelectedSshHost}"
PlaceholderText="Select SSH Host..."
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Margin="0,8" />
<!-- Core fields -->
<TextBlock Text="Customer Name" FontSize="12" />
<TextBox Text="{Binding CustomerName}" Watermark="e.g. Acme Corp" />
<TextBlock Text="Abbreviation (3 letters)" FontSize="12" />
<TextBox Text="{Binding CustomerAbbrev}"
Watermark="e.g. acm"
MaxLength="3" />
<Separator Margin="0,12" />
<!-- Pangolin / Newt (optional) -->
<Expander Header="Pangolin / Newt credentials (optional)">
<StackPanel Spacing="8" Margin="0,8,0,0">
<TextBlock Text="Newt ID" FontSize="12" />
<TextBox Text="{Binding NewtId}" Watermark="(from Pangolin dashboard)" />
<TextBlock Text="Newt Secret" FontSize="12" />
<TextBox Text="{Binding NewtSecret}" PasswordChar="●" Watermark="(from Pangolin dashboard)" />
</StackPanel>
</Expander>
<!-- SMB / CIFS credentials (per-instance, defaults from global settings) -->
<Expander Header="SMB / CIFS credentials">
<StackPanel Spacing="8" Margin="0,8,0,0">
<TextBlock Text="CIFS Server" FontSize="12" />
<TextBox Text="{Binding CifsServer}" Watermark="e.g. 192.168.1.100" />
<TextBlock Text="Share Base Path" FontSize="12" />
<TextBox Text="{Binding CifsShareBasePath}" Watermark="e.g. /share/cms" />
<TextBlock Text="Username" FontSize="12" />
<TextBox Text="{Binding CifsUsername}" Watermark="SMB username" />
<TextBlock Text="Password" FontSize="12" />
<TextBox Text="{Binding CifsPassword}" PasswordChar="●" Watermark="SMB password" />
<TextBlock Text="Extra Options" FontSize="12" />
<TextBox Text="{Binding CifsExtraOptions}" Watermark="file_mode=0777,dir_mode=0777" />
</StackPanel>
</Expander>
<Separator Margin="0,12" />
<!-- Deploy button + progress -->
<Button Content="Deploy Instance"
Command="{Binding DeployCommand}"
IsEnabled="{Binding !IsBusy}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Padding="12,8" FontWeight="SemiBold" />
<!-- Progress bar -->
<Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0"
IsVisible="{Binding IsBusy}">
<ProgressBar Value="{Binding ProgressPercent}"
Maximum="100" Height="6"
CornerRadius="3" />
<TextBlock Grid.Column="1" Text="{Binding ProgressStep}"
FontSize="11" Foreground="#a6adc8"
Margin="8,0,0,0" VerticalAlignment="Center" />
</Grid>
<TextBlock Text="{Binding StatusMessage}" FontSize="12" Foreground="#a6adc8"
Margin="0,4,0,0" TextWrapping="Wrap" />
<!-- Deploy output -->
<TextBox Text="{Binding DeployOutput}" IsReadOnly="True"
AcceptsReturn="True" MaxHeight="260"
FontFamily="Cascadia Mono, Consolas, monospace" FontSize="11"
IsVisible="{Binding DeployOutput.Length}" />
</StackPanel>
<!-- ══ RIGHT COLUMN — live resource preview ══ -->
<Border Grid.Column="2"
Background="#1e1e2e"
CornerRadius="8"
Padding="16,14"
VerticalAlignment="Top">
<StackPanel Spacing="6">
<TextBlock Text="Resource Preview" FontSize="14" FontWeight="SemiBold"
Foreground="#cdd6f4" Margin="0,0,0,8" />
<TextBlock Text="Stack" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewStackName}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#89b4fa" Margin="0,0,0,6" />
<TextBlock Text="Services" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewServiceWeb}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#a6e3a1" />
<TextBlock Text="{Binding PreviewServiceCache}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#a6e3a1" />
<TextBlock Text="{Binding PreviewServiceChart}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#a6e3a1" />
<TextBlock Text="{Binding PreviewServiceNewt}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#a6e3a1" Margin="0,0,0,6" />
<TextBlock Text="Network" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewNetwork}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#94e2d5" Margin="0,0,0,6" />
<TextBlock Text="CIFS Volumes" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewVolCustom}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#f5c2e7" />
<TextBlock Text="{Binding PreviewVolBackup}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#f5c2e7" />
<TextBlock Text="{Binding PreviewVolLibrary}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#f5c2e7" />
<TextBlock Text="{Binding PreviewVolUserscripts}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#f5c2e7" />
<TextBlock Text="{Binding PreviewVolCaCerts}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#f5c2e7" Margin="0,0,0,6" />
<TextBlock Text="Docker Secret" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewSecret}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#fab387" Margin="0,0,0,6" />
<TextBlock Text="MySQL Database" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewMySqlDb}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#cba6f7" />
<TextBlock Text="{Binding PreviewMySqlUser}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#cba6f7" Margin="0,0,0,6" />
<TextBlock Text="CMS URL" FontSize="11" Foreground="#6c7086" />
<TextBlock Text="{Binding PreviewCmsUrl}" FontFamily="Cascadia Mono, Consolas, monospace" FontSize="12" Foreground="#89dceb" />
</StackPanel>
</Border>
</Grid>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class CreateInstanceView : UserControl
{
public CreateInstanceView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,83 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
x:Class="OTSSignsOrchestrator.Desktop.Views.HostsView"
x:DataType="vm:HostsViewModel">
<DockPanel>
<!-- Toolbar -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0,0,0,12">
<Button Content="Add Host" Command="{Binding NewHostCommand}" />
<Button Content="Edit" Command="{Binding EditSelectedHostCommand}" />
<Button Content="Test Connection" Command="{Binding TestConnectionCommand}" />
<Button Content="Delete" Command="{Binding DeleteHostCommand}" />
<Button Content="Refresh" Command="{Binding LoadHostsCommand}" />
</StackPanel>
<!-- Status -->
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Margin="0,8,0,0"
FontSize="12" Foreground="#a6adc8" />
<!-- Edit panel (shown when editing) -->
<Border DockPanel.Dock="Right" Width="350" IsVisible="{Binding IsEditing}"
Background="#1e1e2e" CornerRadius="8" Padding="16" Margin="12,0,0,0">
<ScrollViewer>
<StackPanel Spacing="8">
<TextBlock Text="SSH Host" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,8" />
<TextBlock Text="Label" FontSize="12" />
<TextBox Text="{Binding EditLabel}" Watermark="e.g. Production Swarm" />
<TextBlock Text="Host" FontSize="12" />
<TextBox Text="{Binding EditHost}" Watermark="hostname or IP" />
<TextBlock Text="Port" FontSize="12" />
<NumericUpDown Value="{Binding EditPort}" Minimum="1" Maximum="65535" />
<TextBlock Text="Username" FontSize="12" />
<TextBox Text="{Binding EditUsername}" Watermark="ssh username" />
<CheckBox Content="Use Key Authentication" IsChecked="{Binding EditUseKeyAuth}" />
<TextBlock Text="Private Key Path" FontSize="12"
IsVisible="{Binding EditUseKeyAuth}" />
<TextBox Text="{Binding EditPrivateKeyPath}" Watermark="~/.ssh/id_rsa"
IsVisible="{Binding EditUseKeyAuth}" />
<TextBlock Text="Key Passphrase (optional)" FontSize="12"
IsVisible="{Binding EditUseKeyAuth}" />
<TextBox Text="{Binding EditKeyPassphrase}" PasswordChar="●"
IsVisible="{Binding EditUseKeyAuth}" />
<TextBlock Text="Password (if not using key)" FontSize="12"
IsVisible="{Binding !EditUseKeyAuth}" />
<TextBox Text="{Binding EditPassword}" PasswordChar="●"
IsVisible="{Binding !EditUseKeyAuth}" />
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,12,0,0">
<Button Content="Save" Command="{Binding SaveHostCommand}" />
<Button Content="Cancel" Command="{Binding CancelEditCommand}" />
</StackPanel>
</StackPanel>
</ScrollViewer>
</Border>
<!-- Host list -->
<DataGrid ItemsSource="{Binding Hosts}"
SelectedItem="{Binding SelectedHost}"
AutoGenerateColumns="False"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
CanUserResizeColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Label" Binding="{Binding Label}" Width="150" />
<DataGridTextColumn Header="Host" Binding="{Binding Host}" Width="200" />
<DataGridTextColumn Header="Port" Binding="{Binding Port}" Width="60" />
<DataGridTextColumn Header="User" Binding="{Binding Username}" Width="100" />
<DataGridCheckBoxColumn Header="Key Auth" Binding="{Binding UseKeyAuth}" Width="70" />
<DataGridTextColumn Header="Last Tested" Binding="{Binding LastTestedAt, StringFormat='{}{0:g}'}" Width="150" />
<DataGridCheckBoxColumn Header="OK" Binding="{Binding LastTestSuccess}" Width="50" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class HostsView : UserControl
{
public HostsView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,98 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
x:Class="OTSSignsOrchestrator.Desktop.Views.InstancesView"
x:DataType="vm:InstancesViewModel">
<DockPanel>
<!-- Toolbar -->
<StackPanel DockPanel.Dock="Top" Spacing="8" Margin="0,0,0,12">
<StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox ItemsSource="{Binding AvailableHosts}"
SelectedItem="{Binding SelectedSshHost}"
PlaceholderText="Select SSH Host..."
Width="250">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="List Remote Stacks" Command="{Binding RefreshRemoteStacksCommand}" />
<Button Content="Inspect" Command="{Binding InspectInstanceCommand}" />
<Button Content="Delete" Command="{Binding DeleteInstanceCommand}" />
<Button Content="Refresh" Command="{Binding LoadInstancesCommand}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBox Text="{Binding FilterText}" Watermark="Filter by name..." Width="250" />
<Button Content="Search" Command="{Binding LoadInstancesCommand}" />
</StackPanel>
</StackPanel>
<!-- Status -->
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Margin="0,8,0,0"
FontSize="12" Foreground="#a6adc8" />
<!-- Services panel (shown when inspecting) -->
<Border DockPanel.Dock="Right" Width="350"
IsVisible="{Binding SelectedServices.Count}"
Background="#1e1e2e" CornerRadius="8" Padding="12" Margin="12,0,0,0">
<StackPanel Spacing="4">
<TextBlock Text="Stack Services" FontWeight="SemiBold" Margin="0,0,0,8" />
<ItemsControl ItemsSource="{Binding SelectedServices}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="#313244" CornerRadius="4" Padding="8" Margin="0,2">
<StackPanel Spacing="2">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text="{Binding Image}" FontSize="11" Foreground="#a6adc8" />
<TextBlock Text="{Binding Replicas, StringFormat='Replicas: {0}'}"
FontSize="11" Foreground="#a6adc8" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<!-- Remote stacks panel -->
<Border DockPanel.Dock="Bottom" MaxHeight="200"
IsVisible="{Binding RemoteStacks.Count}"
Background="#1e1e2e" CornerRadius="8" Padding="12" Margin="0,8,0,0">
<StackPanel Spacing="4">
<TextBlock Text="Remote Stacks" FontWeight="SemiBold" />
<ItemsControl ItemsSource="{Binding RemoteStacks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Name}" FontWeight="SemiBold" />
<Run Text="{Binding ServiceCount, StringFormat=' ({0} services)'}"
Foreground="#a6adc8" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<!-- Instance list -->
<DataGrid ItemsSource="{Binding Instances}"
SelectedItem="{Binding SelectedInstance}"
AutoGenerateColumns="False"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
CanUserResizeColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Stack" Binding="{Binding StackName}" Width="120" />
<DataGridTextColumn Header="Customer" Binding="{Binding CustomerName}" Width="120" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="80" />
<DataGridTextColumn Header="Server" Binding="{Binding CmsServerName}" Width="150" />
<DataGridTextColumn Header="Port" Binding="{Binding HostHttpPort}" Width="60" />
<DataGridTextColumn Header="Host" Binding="{Binding SshHost.Label}" Width="120" />
<DataGridTextColumn Header="Created" Binding="{Binding CreatedAt, StringFormat='{}{0:g}'}" Width="140" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class InstancesView : UserControl
{
public InstancesView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
x:Class="OTSSignsOrchestrator.Desktop.Views.LogsView"
x:DataType="vm:LogsViewModel">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0,0,0,12">
<Button Content="Refresh" Command="{Binding LoadLogsCommand}" />
<TextBlock Text="{Binding StatusMessage}" VerticalAlignment="Center"
FontSize="12" Foreground="#a6adc8" Margin="8,0,0,0" />
</StackPanel>
<DataGrid ItemsSource="{Binding Logs}"
AutoGenerateColumns="False"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
CanUserResizeColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Time" Binding="{Binding Timestamp, StringFormat='{}{0:g}'}" Width="150" />
<DataGridTextColumn Header="Operation" Binding="{Binding Operation}" Width="100" />
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="80" />
<DataGridTextColumn Header="Instance" Binding="{Binding Instance.StackName}" Width="120" />
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="*" />
<DataGridTextColumn Header="Duration" Binding="{Binding DurationMs, StringFormat='{}{0}ms'}" Width="80" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class LogsView : UserControl
{
public LogsView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,62 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
xmlns:views="using:OTSSignsOrchestrator.Desktop.Views"
x:Class="OTSSignsOrchestrator.Desktop.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Title="OTS Signs Orchestrator"
Width="1200" Height="800"
WindowStartupLocation="CenterScreen">
<Window.DataTemplates>
<DataTemplate DataType="vm:HostsViewModel">
<views:HostsView />
</DataTemplate>
<DataTemplate DataType="vm:InstancesViewModel">
<views:InstancesView />
</DataTemplate>
<DataTemplate DataType="vm:SecretsViewModel">
<views:SecretsView />
</DataTemplate>
<DataTemplate DataType="vm:LogsViewModel">
<views:LogsView />
</DataTemplate>
<DataTemplate DataType="vm:CreateInstanceViewModel">
<views:CreateInstanceView />
</DataTemplate>
<DataTemplate DataType="vm:SettingsViewModel">
<views:SettingsView />
</DataTemplate>
</Window.DataTemplates>
<DockPanel>
<!-- Status bar -->
<Border DockPanel.Dock="Bottom" Background="#1e1e2e" Padding="8,4">
<TextBlock Text="{Binding StatusMessage}" FontSize="12" Foreground="#a0a0a0" />
</Border>
<!-- Left nav -->
<Border DockPanel.Dock="Left" Width="180" Background="#181825" Padding="0,8">
<StackPanel Spacing="2">
<TextBlock Text="OTS Signs" FontSize="18" FontWeight="Bold" Foreground="#cdd6f4"
Margin="16,8,16,16" />
<ListBox ItemsSource="{Binding NavItems}"
SelectedItem="{Binding SelectedNav}"
Background="Transparent"
Margin="4,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Padding="12,8" FontSize="14" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
<!-- Main content -->
<Border Padding="16">
<ContentControl Content="{Binding CurrentView}" />
</Border>
</DockPanel>
</Window>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,37 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
x:Class="OTSSignsOrchestrator.Desktop.Views.SecretsView"
x:DataType="vm:SecretsViewModel">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0,0,0,12">
<ComboBox ItemsSource="{Binding AvailableHosts}"
SelectedItem="{Binding SelectedSshHost}"
PlaceholderText="Select SSH Host..."
Width="250">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Load Secrets" Command="{Binding LoadSecretsCommand}" />
</StackPanel>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding StatusMessage}" Margin="0,8,0,0"
FontSize="12" Foreground="#a6adc8" />
<DataGrid ItemsSource="{Binding Secrets}"
AutoGenerateColumns="False"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
CanUserResizeColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="200" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="250" />
<DataGridTextColumn Header="Created" Binding="{Binding CreatedAt, StringFormat='{}{0:g}'}" Width="180" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class SecretsView : UserControl
{
public SecretsView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,205 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:OTSSignsOrchestrator.Desktop.ViewModels"
x:Class="OTSSignsOrchestrator.Desktop.Views.SettingsView"
x:DataType="vm:SettingsViewModel">
<DockPanel>
<!-- Top toolbar -->
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="0,0,0,12">
<Button Content="Save All Settings"
Command="{Binding SaveCommand}"
IsEnabled="{Binding !IsBusy}"
FontWeight="SemiBold" Padding="16,8" />
<Button Content="Reload" Command="{Binding LoadCommand}" IsEnabled="{Binding !IsBusy}" />
<TextBlock Text="{Binding StatusMessage}" VerticalAlignment="Center"
FontSize="12" Foreground="#a6adc8" Margin="12,0,0,0" />
</StackPanel>
<!-- Scrollable settings content -->
<ScrollViewer>
<StackPanel Spacing="16" Margin="0,0,16,16" MaxWidth="800">
<!-- ═══ Git Repository ═══ -->
<Border Background="#1e1e2e" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="Git Repository" FontSize="16" FontWeight="SemiBold"
Foreground="#89b4fa" Margin="0,0,0,4" />
<TextBlock Text="Template repository cloned for each new instance."
FontSize="12" Foreground="#6c7086" Margin="0,0,0,4" />
<TextBlock Text="Repository URL" FontSize="12" />
<TextBox Text="{Binding GitRepoUrl}"
Watermark="https://github.com/org/template-repo.git" />
<TextBlock Text="Personal Access Token (PAT)" FontSize="12" />
<TextBox Text="{Binding GitRepoPat}" PasswordChar="●"
Watermark="ghp_xxxx..." />
</StackPanel>
</Border>
<!-- ═══ MySQL Connection ═══ -->
<Border Background="#1e1e2e" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="MySQL Connection" FontSize="16" FontWeight="SemiBold"
Foreground="#a6e3a1" Margin="0,0,0,4" />
<TextBlock Text="Admin credentials used to create databases and users for new instances."
FontSize="12" Foreground="#6c7086" Margin="0,0,0,4" />
<Grid ColumnDefinitions="3*,8,1*" RowDefinitions="Auto,Auto">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Host" FontSize="12" />
<TextBox Text="{Binding MySqlHost}" Watermark="cms-sql.otshosting.app" />
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Port" FontSize="12" />
<TextBox Text="{Binding MySqlPort}" Watermark="3306" />
</StackPanel>
</Grid>
<TextBlock Text="Admin Username" FontSize="12" />
<TextBox Text="{Binding MySqlAdminUser}" Watermark="root" />
<TextBlock Text="Admin Password" FontSize="12" />
<TextBox Text="{Binding MySqlAdminPassword}" PasswordChar="●" />
<Button Content="Test MySQL Connection"
Command="{Binding TestMySqlConnectionCommand}"
IsEnabled="{Binding !IsBusy}"
Margin="0,4,0,0" />
</StackPanel>
</Border>
<!-- ═══ SMTP Settings ═══ -->
<Border Background="#1e1e2e" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="SMTP Settings" FontSize="16" FontWeight="SemiBold"
Foreground="#f5c2e7" Margin="0,0,0,4" />
<TextBlock Text="Email configuration applied to all CMS instances."
FontSize="12" Foreground="#6c7086" Margin="0,0,0,4" />
<TextBlock Text="SMTP Server (host:port)" FontSize="12" />
<TextBox Text="{Binding SmtpServer}" Watermark="smtp.azurecomm.net:587" />
<TextBlock Text="SMTP Username" FontSize="12" />
<TextBox Text="{Binding SmtpUsername}" Watermark="user@domain.com" />
<TextBlock Text="SMTP Password" FontSize="12" />
<TextBox Text="{Binding SmtpPassword}" PasswordChar="●" />
<StackPanel Orientation="Horizontal" Spacing="16">
<CheckBox Content="Use TLS" IsChecked="{Binding SmtpUseTls}" />
<CheckBox Content="Use STARTTLS" IsChecked="{Binding SmtpUseStartTls}" />
</StackPanel>
<TextBlock Text="Rewrite Domain" FontSize="12" />
<TextBox Text="{Binding SmtpRewriteDomain}" Watermark="ots-signs.com" />
<TextBlock Text="SMTP Hostname" FontSize="12" />
<TextBox Text="{Binding SmtpHostname}" Watermark="demo.ots-signs.com" />
<TextBlock Text="From Line Override" FontSize="12" />
<TextBox Text="{Binding SmtpFromLineOverride}" Watermark="NO" />
</StackPanel>
</Border>
<!-- ═══ Pangolin / Newt ═══ -->
<Border Background="#1e1e2e" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="Pangolin (Newt Tunnel)" FontSize="16" FontWeight="SemiBold"
Foreground="#fab387" Margin="0,0,0,4" />
<TextBlock Text="Global Pangolin endpoint. Newt ID and Secret are configured per-instance."
FontSize="12" Foreground="#6c7086" Margin="0,0,0,4" />
<TextBlock Text="Pangolin Endpoint URL" FontSize="12" />
<TextBox Text="{Binding PangolinEndpoint}" Watermark="https://app.pangolin.net" />
</StackPanel>
</Border>
<!-- ═══ CIFS Volumes ═══ -->
<Border Background="#1e1e2e" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="CIFS Volumes" FontSize="16" FontWeight="SemiBold"
Foreground="#cba6f7" Margin="0,0,0,4" />
<TextBlock Text="Network share settings for Docker volumes. Volumes will be mounted via CIFS."
FontSize="12" Foreground="#6c7086" Margin="0,0,0,4" />
<TextBlock Text="CIFS Server (hostname/IP)" FontSize="12" />
<TextBox Text="{Binding CifsServer}" Watermark="nas.local" />
<TextBlock Text="Share Base Path" FontSize="12" />
<TextBox Text="{Binding CifsShareBasePath}" Watermark="/share/cms" />
<TextBlock Text="Username" FontSize="12" />
<TextBox Text="{Binding CifsUsername}" Watermark="smbuser" />
<TextBlock Text="Password" FontSize="12" />
<TextBox Text="{Binding CifsPassword}" PasswordChar="●" />
<TextBlock Text="Extra Mount Options" FontSize="12" />
<TextBox Text="{Binding CifsOptions}" Watermark="file_mode=0777,dir_mode=0777" />
</StackPanel>
</Border>
<!-- ═══ Instance Defaults ═══ -->
<Border Background="#1e1e2e" CornerRadius="8" Padding="16">
<StackPanel Spacing="8">
<TextBlock Text="Instance Defaults" FontSize="16" FontWeight="SemiBold"
Foreground="#89dceb" Margin="0,0,0,4" />
<TextBlock Text="Default Docker images, naming templates, and PHP settings for new instances. Use {abbrev} as a placeholder for the customer abbreviation."
FontSize="12" Foreground="#6c7086" Margin="0,0,0,4" TextWrapping="Wrap" />
<TextBlock Text="Docker Images" FontSize="13" FontWeight="SemiBold" Margin="0,8,0,4" />
<TextBlock Text="CMS Image" FontSize="12" />
<TextBox Text="{Binding DefaultCmsImage}"
Watermark="ghcr.io/xibosignage/xibo-cms:release-4.2.3" />
<TextBlock Text="Newt Image" FontSize="12" />
<TextBox Text="{Binding DefaultNewtImage}" Watermark="fosrl/newt" />
<TextBlock Text="Memcached Image" FontSize="12" />
<TextBox Text="{Binding DefaultMemcachedImage}" Watermark="memcached:alpine" />
<TextBlock Text="QuickChart Image" FontSize="12" />
<TextBox Text="{Binding DefaultQuickChartImage}" Watermark="ianw/quickchart" />
<TextBlock Text="Naming Templates" FontSize="13" FontWeight="SemiBold" Margin="0,12,0,4" />
<TextBlock Text="CMS Server Name Template" FontSize="12" />
<TextBox Text="{Binding DefaultCmsServerNameTemplate}"
Watermark="{}{abbrev}.ots-signs.com" />
<TextBlock Text="Theme Host Path Template" FontSize="12" />
<TextBox Text="{Binding DefaultThemeHostPath}"
Watermark="/cms/{abbrev}-cms-theme-custom" />
<TextBlock Text="MySQL Database Name Template" FontSize="12" />
<TextBox Text="{Binding DefaultMySqlDbTemplate}" Watermark="{}{abbrev}_cms_db" />
<TextBlock Text="MySQL User Template" FontSize="12" />
<TextBox Text="{Binding DefaultMySqlUserTemplate}" Watermark="{}{abbrev}_cms" />
<TextBlock Text="PHP Settings" FontSize="13" FontWeight="SemiBold" Margin="0,12,0,4" />
<Grid ColumnDefinitions="1*,8,1*,8,1*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Post Max Size" FontSize="12" />
<TextBox Text="{Binding DefaultPhpPostMaxSize}" Watermark="10G" />
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Upload Max Filesize" FontSize="12" />
<TextBox Text="{Binding DefaultPhpUploadMaxFilesize}" Watermark="10G" />
</StackPanel>
<StackPanel Grid.Column="4" Spacing="4">
<TextBlock Text="Max Execution Time" FontSize="12" />
<TextBox Text="{Binding DefaultPhpMaxExecutionTime}" Watermark="600" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace OTSSignsOrchestrator.Desktop.Views;
public partial class SettingsView : UserControl
{
public SettingsView()
{
InitializeComponent();
}
}