feat: Implement provisioning pipelines for subscription management

- Add ReactivatePipeline to handle subscription reactivation, including scaling Docker services, health verification, status updates, audit logging, and broadcasting status changes.
- Introduce RotateCredentialsPipeline for OAuth2 credential rotation, managing the deletion of old apps, creation of new ones, credential storage, access verification, and audit logging.
- Create StepRunner to manage job step execution, including lifecycle management and progress broadcasting via SignalR.
- Implement SuspendPipeline for subscription suspension, scaling down services, updating statuses, logging audits, and broadcasting changes.
- Add UpdateScreenLimitPipeline to update Xibo CMS screen limits and record snapshots.
- Introduce XiboFeatureManifests for hardcoded feature ACLs per role.
- Add docker-compose.dev.yml for local development with PostgreSQL setup.
This commit is contained in:
Matt Batchelder
2026-03-18 10:27:26 -04:00
parent c2e03de8bb
commit c6d46098dd
77 changed files with 9412 additions and 29 deletions

View File

@@ -5,8 +5,12 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;
using Polly.Extensions.Http;
using Refit;
using Serilog;
using OTSSignsOrchestrator.Core.Configuration;
using OTSSignsOrchestrator.Core.Data;
@@ -81,10 +85,26 @@ public class App : Application
window.Activate();
Log.Information("MainWindow Show() + Activate() called");
// Start the SignalR connection (fire-and-forget, reconnect handles failures)
_ = Task.Run(async () =>
{
try
{
var signalR = Services.GetRequiredService<ServerSignalRService>();
await signalR.StartAsync();
}
catch (Exception ex)
{
Log.Warning(ex, "Failed to start SignalR connection on startup");
}
});
desktop.ShutdownRequested += (_, _) =>
{
var ssh = Services.GetService<SshConnectionService>();
ssh?.Dispose();
var signalR = Services.GetService<ServerSignalRService>();
signalR?.DisposeAsync().AsTask().GetAwaiter().GetResult();
};
}
else
@@ -140,6 +160,20 @@ public class App : Application
services.AddHttpClient("XiboHealth");
services.AddHttpClient("AuthentikApi");
// ── Server API integration ──────────────────────────────────────────
services.AddSingleton<TokenStoreService>();
services.AddTransient<AuthHeaderHandler>();
var serverBaseUrl = config["ServerBaseUrl"] ?? "https://localhost:5001";
services.AddRefitClient<IServerApiClient>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(serverBaseUrl))
.AddHttpMessageHandler<AuthHeaderHandler>()
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));
services.AddSingleton<ServerSignalRService>();
// SSH services (singletons — maintain connections)
services.AddSingleton<SshConnectionService>();