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,41 @@
using System.ComponentModel.DataAnnotations;
namespace OTSSignsOrchestrator.Core.Models.DTOs;
public class CreateInstanceDto
{
[Required, MaxLength(100)]
public string CustomerName { get; set; } = string.Empty;
/// <summary>Exactly 3 lowercase letters used to derive all resource names.</summary>
[Required, MaxLength(3), MinLength(3), RegularExpression("^[a-z]{3}$", ErrorMessage = "Abbreviation must be exactly 3 lowercase letters.")]
public string CustomerAbbrev { get; set; } = string.Empty;
/// <summary>SSH host to deploy to.</summary>
public Guid? SshHostId { get; set; }
/// <summary>Pangolin Newt ID (optional — tunnel service excluded if not provided).</summary>
[MaxLength(500)]
public string? NewtId { get; set; }
/// <summary>Pangolin Newt Secret (optional — tunnel service excluded if not provided).</summary>
[MaxLength(500)]
public string? NewtSecret { get; set; }
// ── CIFS / SMB credentials (optional — falls back to global settings) ──
[MaxLength(200)]
public string? CifsServer { get; set; }
[MaxLength(500)]
public string? CifsShareBasePath { get; set; }
[MaxLength(200)]
public string? CifsUsername { get; set; }
[MaxLength(500)]
public string? CifsPassword { get; set; }
[MaxLength(500)]
public string? CifsExtraOptions { get; set; }
}

View File

@@ -0,0 +1,13 @@
namespace OTSSignsOrchestrator.Core.Models.DTOs;
public class DeploymentResultDto
{
public bool Success { get; set; }
public string StackName { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public string? Output { get; set; }
public string? ErrorMessage { get; set; }
public int ExitCode { get; set; }
public long DurationMs { get; set; }
public int ServiceCount { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace OTSSignsOrchestrator.Core.Models.DTOs;
public class TemplateConfig
{
public string Yaml { get; set; } = string.Empty;
public List<string> EnvLines { get; set; } = new();
public DateTime FetchedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,43 @@
using System.ComponentModel.DataAnnotations;
namespace OTSSignsOrchestrator.Core.Models.DTOs;
public class UpdateInstanceDto
{
[MaxLength(500)]
public string? TemplateRepoUrl { get; set; }
[MaxLength(500)]
public string? TemplateRepoPat { get; set; }
[MaxLength(200)]
public string? SmtpServer { get; set; }
[MaxLength(200)]
public string? SmtpUsername { get; set; }
public List<string>? Constraints { get; set; }
[MaxLength(200)]
public string? XiboUsername { get; set; }
[MaxLength(200)]
public string? XiboPassword { get; set; }
// ── CIFS / SMB credentials (per-instance) ──
[MaxLength(200)]
public string? CifsServer { get; set; }
[MaxLength(500)]
public string? CifsShareBasePath { get; set; }
[MaxLength(200)]
public string? CifsUsername { get; set; }
[MaxLength(500)]
public string? CifsPassword { get; set; }
[MaxLength(500)]
public string? CifsExtraOptions { get; set; }
}

View File

@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace OTSSignsOrchestrator.Core.Models.Entities;
/// <summary>
/// Key-value application setting persisted in the local database.
/// Sensitive values are encrypted at rest via DataProtection.
/// </summary>
public class AppSetting
{
[Key, MaxLength(200)]
public string Key { get; set; } = string.Empty;
[MaxLength(4000)]
public string? Value { get; set; }
[Required, MaxLength(50)]
public string Category { get; set; } = string.Empty;
public bool IsSensitive { get; set; }
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,117 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace OTSSignsOrchestrator.Core.Models.Entities;
public enum InstanceStatus
{
Deploying,
Active,
Error,
Deleted
}
public enum XiboApiTestStatus
{
Unknown,
Success,
Failed
}
public class CmsInstance
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required, MaxLength(100)]
public string CustomerName { get; set; } = string.Empty;
/// <summary>Exactly 3 lowercase letters used to derive all resource names.</summary>
[MaxLength(3)]
public string CustomerAbbrev { get; set; } = string.Empty;
[Required, MaxLength(100)]
public string StackName { get; set; } = string.Empty;
[Required, MaxLength(200)]
public string CmsServerName { get; set; } = string.Empty;
[Required, Range(1024, 65535)]
public int HostHttpPort { get; set; }
[Required, MaxLength(500)]
public string ThemeHostPath { get; set; } = string.Empty;
[Required, MaxLength(500)]
public string LibraryHostPath { get; set; } = string.Empty;
[Required, MaxLength(200)]
public string SmtpServer { get; set; } = string.Empty;
[Required, MaxLength(200)]
public string SmtpUsername { get; set; } = string.Empty;
/// <summary>
/// JSON array of placement constraints, e.g. ["node.labels.xibo==true"]
/// </summary>
[MaxLength(2000)]
public string? Constraints { get; set; }
[Required, MaxLength(500)]
public string TemplateRepoUrl { get; set; } = string.Empty;
[MaxLength(500)]
public string? TemplateRepoPat { get; set; }
public DateTime? TemplateLastFetch { get; set; }
[MaxLength(100)]
public string? TemplateCacheKey { get; set; }
public InstanceStatus Status { get; set; } = InstanceStatus.Deploying;
/// <summary>Encrypted Xibo admin username for API access.</summary>
[MaxLength(500)]
public string? XiboUsername { get; set; }
/// <summary>Encrypted Xibo admin password. Never logged; encrypted at rest.</summary>
[MaxLength(1000)]
public string? XiboPassword { get; set; }
public XiboApiTestStatus XiboApiTestStatus { get; set; } = XiboApiTestStatus.Unknown;
public DateTime? XiboApiTestedAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
/// <summary>Soft delete marker.</summary>
public DateTime? DeletedAt { get; set; }
// ── CIFS / SMB credentials (per-instance) ─────────────────────────────
[MaxLength(200)]
public string? CifsServer { get; set; }
[MaxLength(500)]
public string? CifsShareBasePath { get; set; }
[MaxLength(200)]
public string? CifsUsername { get; set; }
/// <summary>Encrypted CIFS password. Never logged; encrypted at rest.</summary>
[MaxLength(1000)]
public string? CifsPassword { get; set; }
[MaxLength(500)]
public string? CifsExtraOptions { get; set; }
/// <summary>ID of the SshHost this instance is deployed to.</summary>
public Guid? SshHostId { get; set; }
[ForeignKey(nameof(SshHostId))]
public SshHost? SshHost { get; set; }
public ICollection<OperationLog> OperationLogs { get; set; } = new List<OperationLog>();
}

View File

@@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace OTSSignsOrchestrator.Core.Models.Entities;
public enum OperationType
{
Create,
Update,
Delete,
TestXibo,
TestIdP,
DeploymentStatus,
Other
}
public enum OperationStatus
{
Pending,
Success,
Failure
}
public class OperationLog
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
public OperationType Operation { get; set; }
public Guid? InstanceId { get; set; }
[ForeignKey(nameof(InstanceId))]
public CmsInstance? Instance { get; set; }
[MaxLength(200)]
public string? UserId { get; set; }
public OperationStatus Status { get; set; } = OperationStatus.Pending;
/// <summary>Human-readable message. NEVER includes secret values.</summary>
[MaxLength(2000)]
public string? Message { get; set; }
public long? DurationMs { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

View File

@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
namespace OTSSignsOrchestrator.Core.Models.Entities;
public class SecretMetadata
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required, MaxLength(200)]
public string Name { get; set; } = string.Empty;
public bool IsGlobal { get; set; }
[MaxLength(100)]
public string? CustomerName { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastRotatedAt { get; set; }
}

View File

@@ -0,0 +1,59 @@
using System.ComponentModel.DataAnnotations;
namespace OTSSignsOrchestrator.Core.Models.Entities;
/// <summary>
/// Represents a remote Docker Swarm host accessible over SSH.
/// SSH key paths or encrypted passwords are stored for authentication.
/// </summary>
public class SshHost
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required, MaxLength(200)]
public string Label { get; set; } = string.Empty;
[Required, MaxLength(500)]
public string Host { get; set; } = string.Empty;
[Range(1, 65535)]
public int Port { get; set; } = 22;
[Required, MaxLength(100)]
public string Username { get; set; } = string.Empty;
/// <summary>
/// Path to the SSH private key file on the local machine.
/// </summary>
[MaxLength(1000)]
public string? PrivateKeyPath { get; set; }
/// <summary>
/// Encrypted passphrase for the SSH key (if any). Protected by DataProtection.
/// </summary>
[MaxLength(2000)]
public string? KeyPassphrase { get; set; }
/// <summary>
/// Encrypted SSH password (if key-based auth is not used). Protected by DataProtection.
/// </summary>
[MaxLength(2000)]
public string? Password { get; set; }
/// <summary>
/// Whether to prefer key-based auth over password.
/// </summary>
public bool UseKeyAuth { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
/// <summary>Last time a connection was successfully tested.</summary>
public DateTime? LastTestedAt { get; set; }
public bool? LastTestSuccess { get; set; }
public ICollection<CmsInstance> Instances { get; set; } = new List<CmsInstance>();
}