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.
249 lines
14 KiB
C#
249 lines
14 KiB
C#
using System.Collections.ObjectModel;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using OTSSignsOrchestrator.Core.Services;
|
|
|
|
namespace OTSSignsOrchestrator.Desktop.ViewModels;
|
|
|
|
/// <summary>
|
|
/// ViewModel for the Settings page — manages Git, MySQL, SMTP, Pangolin, CIFS,
|
|
/// and Instance Defaults configuration, persisted via SettingsService.
|
|
/// </summary>
|
|
public partial class SettingsViewModel : ObservableObject
|
|
{
|
|
private readonly IServiceProvider _services;
|
|
|
|
[ObservableProperty] private string _statusMessage = string.Empty;
|
|
[ObservableProperty] private bool _isBusy;
|
|
|
|
// ── Git ──────────────────────────────────────────────────────────────────
|
|
[ObservableProperty] private string _gitRepoUrl = string.Empty;
|
|
[ObservableProperty] private string _gitRepoPat = string.Empty;
|
|
|
|
// ── MySQL Admin ─────────────────────────────────────────────────────────
|
|
[ObservableProperty] private string _mySqlHost = string.Empty;
|
|
[ObservableProperty] private string _mySqlPort = "3306";
|
|
[ObservableProperty] private string _mySqlAdminUser = string.Empty;
|
|
[ObservableProperty] private string _mySqlAdminPassword = string.Empty;
|
|
|
|
// ── SMTP ────────────────────────────────────────────────────────────────
|
|
[ObservableProperty] private string _smtpServer = string.Empty;
|
|
[ObservableProperty] private string _smtpUsername = string.Empty;
|
|
[ObservableProperty] private string _smtpPassword = string.Empty;
|
|
[ObservableProperty] private bool _smtpUseTls = true;
|
|
[ObservableProperty] private bool _smtpUseStartTls = true;
|
|
[ObservableProperty] private string _smtpRewriteDomain = string.Empty;
|
|
[ObservableProperty] private string _smtpHostname = string.Empty;
|
|
[ObservableProperty] private string _smtpFromLineOverride = "NO";
|
|
|
|
// ── Pangolin ────────────────────────────────────────────────────────────
|
|
[ObservableProperty] private string _pangolinEndpoint = "https://app.pangolin.net";
|
|
|
|
// ── CIFS ────────────────────────────────────────────────────────────────
|
|
[ObservableProperty] private string _cifsServer = string.Empty;
|
|
[ObservableProperty] private string _cifsShareBasePath = string.Empty;
|
|
[ObservableProperty] private string _cifsUsername = string.Empty;
|
|
[ObservableProperty] private string _cifsPassword = string.Empty;
|
|
[ObservableProperty] private string _cifsOptions = "file_mode=0777,dir_mode=0777";
|
|
|
|
// ── Instance Defaults ───────────────────────────────────────────────────
|
|
[ObservableProperty] private string _defaultCmsImage = "ghcr.io/xibosignage/xibo-cms:release-4.2.3";
|
|
[ObservableProperty] private string _defaultNewtImage = "fosrl/newt";
|
|
[ObservableProperty] private string _defaultMemcachedImage = "memcached:alpine";
|
|
[ObservableProperty] private string _defaultQuickChartImage = "ianw/quickchart";
|
|
[ObservableProperty] private string _defaultCmsServerNameTemplate = "{abbrev}.ots-signs.com";
|
|
[ObservableProperty] private string _defaultThemeHostPath = "/cms/{abbrev}-cms-theme-custom";
|
|
[ObservableProperty] private string _defaultMySqlDbTemplate = "{abbrev}_cms_db";
|
|
[ObservableProperty] private string _defaultMySqlUserTemplate = "{abbrev}_cms";
|
|
[ObservableProperty] private string _defaultPhpPostMaxSize = "10G";
|
|
[ObservableProperty] private string _defaultPhpUploadMaxFilesize = "10G";
|
|
[ObservableProperty] private string _defaultPhpMaxExecutionTime = "600";
|
|
|
|
public SettingsViewModel(IServiceProvider services)
|
|
{
|
|
_services = services;
|
|
_ = LoadAsync();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task LoadAsync()
|
|
{
|
|
IsBusy = true;
|
|
try
|
|
{
|
|
using var scope = _services.CreateScope();
|
|
var svc = scope.ServiceProvider.GetRequiredService<SettingsService>();
|
|
|
|
// Git
|
|
GitRepoUrl = await svc.GetAsync(SettingsService.GitRepoUrl, string.Empty);
|
|
GitRepoPat = await svc.GetAsync(SettingsService.GitRepoPat, string.Empty);
|
|
|
|
// MySQL
|
|
MySqlHost = await svc.GetAsync(SettingsService.MySqlHost, string.Empty);
|
|
MySqlPort = await svc.GetAsync(SettingsService.MySqlPort, "3306");
|
|
MySqlAdminUser = await svc.GetAsync(SettingsService.MySqlAdminUser, string.Empty);
|
|
MySqlAdminPassword = await svc.GetAsync(SettingsService.MySqlAdminPassword, string.Empty);
|
|
|
|
// SMTP
|
|
SmtpServer = await svc.GetAsync(SettingsService.SmtpServer, string.Empty);
|
|
SmtpUsername = await svc.GetAsync(SettingsService.SmtpUsername, string.Empty);
|
|
SmtpPassword = await svc.GetAsync(SettingsService.SmtpPassword, string.Empty);
|
|
SmtpUseTls = (await svc.GetAsync(SettingsService.SmtpUseTls, "YES")) == "YES";
|
|
SmtpUseStartTls = (await svc.GetAsync(SettingsService.SmtpUseStartTls, "YES")) == "YES";
|
|
SmtpRewriteDomain = await svc.GetAsync(SettingsService.SmtpRewriteDomain, string.Empty);
|
|
SmtpHostname = await svc.GetAsync(SettingsService.SmtpHostname, string.Empty);
|
|
SmtpFromLineOverride = await svc.GetAsync(SettingsService.SmtpFromLineOverride, "NO");
|
|
|
|
// Pangolin
|
|
PangolinEndpoint = await svc.GetAsync(SettingsService.PangolinEndpoint, "https://app.pangolin.net");
|
|
|
|
// CIFS
|
|
CifsServer = await svc.GetAsync(SettingsService.CifsServer, string.Empty);
|
|
CifsShareBasePath = await svc.GetAsync(SettingsService.CifsShareBasePath, string.Empty);
|
|
CifsUsername = await svc.GetAsync(SettingsService.CifsUsername, string.Empty);
|
|
CifsPassword = await svc.GetAsync(SettingsService.CifsPassword, string.Empty);
|
|
CifsOptions = await svc.GetAsync(SettingsService.CifsOptions, "file_mode=0777,dir_mode=0777");
|
|
|
|
// Instance Defaults
|
|
DefaultCmsImage = await svc.GetAsync(SettingsService.DefaultCmsImage, "ghcr.io/xibosignage/xibo-cms:release-4.2.3");
|
|
DefaultNewtImage = await svc.GetAsync(SettingsService.DefaultNewtImage, "fosrl/newt");
|
|
DefaultMemcachedImage = await svc.GetAsync(SettingsService.DefaultMemcachedImage, "memcached:alpine");
|
|
DefaultQuickChartImage = await svc.GetAsync(SettingsService.DefaultQuickChartImage, "ianw/quickchart");
|
|
DefaultCmsServerNameTemplate = await svc.GetAsync(SettingsService.DefaultCmsServerNameTemplate, "{abbrev}.ots-signs.com");
|
|
DefaultThemeHostPath = await svc.GetAsync(SettingsService.DefaultThemeHostPath, "/cms/{abbrev}-cms-theme-custom");
|
|
DefaultMySqlDbTemplate = await svc.GetAsync(SettingsService.DefaultMySqlDbTemplate, "{abbrev}_cms_db");
|
|
DefaultMySqlUserTemplate = await svc.GetAsync(SettingsService.DefaultMySqlUserTemplate, "{abbrev}_cms");
|
|
DefaultPhpPostMaxSize = await svc.GetAsync(SettingsService.DefaultPhpPostMaxSize, "10G");
|
|
DefaultPhpUploadMaxFilesize = await svc.GetAsync(SettingsService.DefaultPhpUploadMaxFilesize, "10G");
|
|
DefaultPhpMaxExecutionTime = await svc.GetAsync(SettingsService.DefaultPhpMaxExecutionTime, "600");
|
|
|
|
StatusMessage = "Settings loaded.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Error loading settings: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task SaveAsync()
|
|
{
|
|
IsBusy = true;
|
|
try
|
|
{
|
|
using var scope = _services.CreateScope();
|
|
var svc = scope.ServiceProvider.GetRequiredService<SettingsService>();
|
|
|
|
var settings = new List<(string Key, string? Value, string Category, bool IsSensitive)>
|
|
{
|
|
// Git
|
|
(SettingsService.GitRepoUrl, NullIfEmpty(GitRepoUrl), SettingsService.CatGit, false),
|
|
(SettingsService.GitRepoPat, NullIfEmpty(GitRepoPat), SettingsService.CatGit, true),
|
|
|
|
// MySQL
|
|
(SettingsService.MySqlHost, NullIfEmpty(MySqlHost), SettingsService.CatMySql, false),
|
|
(SettingsService.MySqlPort, MySqlPort, SettingsService.CatMySql, false),
|
|
(SettingsService.MySqlAdminUser, NullIfEmpty(MySqlAdminUser), SettingsService.CatMySql, false),
|
|
(SettingsService.MySqlAdminPassword, NullIfEmpty(MySqlAdminPassword), SettingsService.CatMySql, true),
|
|
|
|
// SMTP
|
|
(SettingsService.SmtpServer, NullIfEmpty(SmtpServer), SettingsService.CatSmtp, false),
|
|
(SettingsService.SmtpUsername, NullIfEmpty(SmtpUsername), SettingsService.CatSmtp, false),
|
|
(SettingsService.SmtpPassword, NullIfEmpty(SmtpPassword), SettingsService.CatSmtp, true),
|
|
(SettingsService.SmtpUseTls, SmtpUseTls ? "YES" : "NO", SettingsService.CatSmtp, false),
|
|
(SettingsService.SmtpUseStartTls, SmtpUseStartTls ? "YES" : "NO", SettingsService.CatSmtp, false),
|
|
(SettingsService.SmtpRewriteDomain, NullIfEmpty(SmtpRewriteDomain), SettingsService.CatSmtp, false),
|
|
(SettingsService.SmtpHostname, NullIfEmpty(SmtpHostname), SettingsService.CatSmtp, false),
|
|
(SettingsService.SmtpFromLineOverride, SmtpFromLineOverride, SettingsService.CatSmtp, false),
|
|
|
|
// Pangolin
|
|
(SettingsService.PangolinEndpoint, NullIfEmpty(PangolinEndpoint), SettingsService.CatPangolin, false),
|
|
|
|
// CIFS
|
|
(SettingsService.CifsServer, NullIfEmpty(CifsServer), SettingsService.CatCifs, false),
|
|
(SettingsService.CifsShareBasePath, NullIfEmpty(CifsShareBasePath), SettingsService.CatCifs, false),
|
|
(SettingsService.CifsUsername, NullIfEmpty(CifsUsername), SettingsService.CatCifs, false),
|
|
(SettingsService.CifsPassword, NullIfEmpty(CifsPassword), SettingsService.CatCifs, true),
|
|
(SettingsService.CifsOptions, NullIfEmpty(CifsOptions), SettingsService.CatCifs, false),
|
|
|
|
// Instance Defaults
|
|
(SettingsService.DefaultCmsImage, DefaultCmsImage, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultNewtImage, DefaultNewtImage, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultMemcachedImage, DefaultMemcachedImage, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultQuickChartImage, DefaultQuickChartImage, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultCmsServerNameTemplate, DefaultCmsServerNameTemplate, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultThemeHostPath, DefaultThemeHostPath, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultMySqlDbTemplate, DefaultMySqlDbTemplate, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultMySqlUserTemplate, DefaultMySqlUserTemplate, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultPhpPostMaxSize, DefaultPhpPostMaxSize, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultPhpUploadMaxFilesize, DefaultPhpUploadMaxFilesize, SettingsService.CatDefaults, false),
|
|
(SettingsService.DefaultPhpMaxExecutionTime, DefaultPhpMaxExecutionTime, SettingsService.CatDefaults, false),
|
|
};
|
|
|
|
await svc.SaveManyAsync(settings);
|
|
StatusMessage = "Settings saved successfully.";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Error saving settings: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task TestMySqlConnectionAsync()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(MySqlHost) || string.IsNullOrWhiteSpace(MySqlAdminUser))
|
|
{
|
|
StatusMessage = "MySQL Host and Admin User are required for connection test.";
|
|
return;
|
|
}
|
|
|
|
IsBusy = true;
|
|
StatusMessage = "Testing MySQL connection via SSH...";
|
|
try
|
|
{
|
|
// The test runs a mysql --version or a simple SELECT 1 query via SSH
|
|
// We need an SshHost to route through — use the first available
|
|
using var scope = _services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<OTSSignsOrchestrator.Core.Data.XiboContext>();
|
|
var host = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
|
|
.FirstOrDefaultAsync(db.SshHosts);
|
|
|
|
if (host == null)
|
|
{
|
|
StatusMessage = "No SSH hosts configured. Add one in the Hosts page first.";
|
|
return;
|
|
}
|
|
|
|
var ssh = _services.GetRequiredService<Services.SshConnectionService>();
|
|
var port = int.TryParse(MySqlPort, out var p) ? p : 3306;
|
|
var cmd = $"mysql -h {MySqlHost} -P {port} -u {MySqlAdminUser} -p'{MySqlAdminPassword.Replace("'", "'\\''")}' -e 'SELECT 1' 2>&1";
|
|
var (exitCode, stdout, stderr) = await ssh.RunCommandAsync(host, cmd);
|
|
|
|
StatusMessage = exitCode == 0
|
|
? $"MySQL connection successful via {host.Label}."
|
|
: $"MySQL connection failed: {(string.IsNullOrWhiteSpace(stderr) ? stdout : stderr).Trim()}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"MySQL test error: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
IsBusy = false;
|
|
}
|
|
}
|
|
|
|
private static string? NullIfEmpty(string? value)
|
|
=> string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
|
}
|