148 lines
5.0 KiB
Plaintext
148 lines
5.0 KiB
Plaintext
@using SilverOS.Welcome.Core.Apps
|
|
@using SilverOS.Welcome.Core.Preconfig
|
|
@inject IApplyService ApplyService
|
|
@inject IAppCatalog AppCatalog
|
|
@inject IPreconfigStore PreconfigStore
|
|
@inject WizardState State
|
|
|
|
<div class="step apply-step">
|
|
<div class="apply-header">
|
|
<h1>Applying Configuration</h1>
|
|
<p class="step-subtitle">Your SilverOS is being configured. This may take a few minutes.</p>
|
|
</div>
|
|
|
|
@if (_errorMessage is not null)
|
|
{
|
|
<div class="apply-error">
|
|
<div class="apply-error-icon">⚠</div>
|
|
<p class="apply-error-title">Configuration failed</p>
|
|
<p class="apply-error-detail">@SanitiseForDisplay(_errorMessage)</p>
|
|
<button class="btn-primary btn-retry" @onclick="() => _ = StartAsync()">Retry</button>
|
|
</div>
|
|
}
|
|
else if (!_running && !_complete)
|
|
{
|
|
<div class="apply-ready">
|
|
<p class="apply-ready-text">Ready to apply your selections. Click Start to begin.</p>
|
|
<button class="btn-primary btn-start" @onclick="() => _ = StartAsync()" disabled="@_running">Start</button>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="apply-progress-container">
|
|
<div class="apply-stage-label">@_stageLabel</div>
|
|
<div class="apply-progress-track">
|
|
<div class="apply-progress-bar" style="width: @(_percent)%"></div>
|
|
</div>
|
|
<div class="apply-percent-label">@(_percent)%</div>
|
|
</div>
|
|
|
|
@if (_complete)
|
|
{
|
|
<div class="apply-complete">
|
|
<div class="apply-complete-icon">✓</div>
|
|
<p>Configuration complete.</p>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
[Parameter] public EventCallback OnComplete { get; set; }
|
|
[Parameter] public EventCallback<bool> OnRunningChanged { get; set; }
|
|
[Parameter] public bool AutoStart { get; set; }
|
|
|
|
private bool _running;
|
|
private bool _autoStarted;
|
|
private bool _complete;
|
|
private int _percent;
|
|
private string _stageLabel = "Preparing…";
|
|
private string? _errorMessage;
|
|
|
|
private const int ErrorDisplayMaxLength = 200;
|
|
|
|
/// <summary>
|
|
/// Strips newlines and caps length so a multi-line or huge error message
|
|
/// cannot dump raw output into the UI.
|
|
/// </summary>
|
|
private static string SanitiseForDisplay(string message)
|
|
{
|
|
var single = message.ReplaceLineEndings(" ").Trim();
|
|
return single.Length <= ErrorDisplayMaxLength
|
|
? single
|
|
: single[..ErrorDisplayMaxLength] + "…";
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
// First-run auto-apply: when the host jumps straight to this step with AutoStart,
|
|
// kick off the same apply the Start button would, exactly once. The manual path
|
|
// (AutoStart=false) is untouched.
|
|
if (firstRender && AutoStart && !_autoStarted)
|
|
{
|
|
_autoStarted = true;
|
|
await StartAsync();
|
|
}
|
|
}
|
|
|
|
public async Task StartAsync()
|
|
{
|
|
// Re-entrancy guard: prevent a second overlapping apply if already running
|
|
// (e.g. rapid double-click on Retry).
|
|
if (_running) return;
|
|
|
|
_running = true;
|
|
_complete = false;
|
|
_errorMessage = null;
|
|
_percent = 0;
|
|
_stageLabel = "Preparing…";
|
|
StateHasChanged();
|
|
await OnRunningChanged.InvokeAsync(true);
|
|
|
|
var apps = AppCatalog.Load(Path.Combine(AppContext.BaseDirectory, "apps"))
|
|
.All.Where(a => State.SelectedApps.Contains(a.Id)).ToList();
|
|
|
|
// D1: Apply is now apps+bitlocker only (account via Setup, hardening via SetupComplete).
|
|
// D2 owns the full UI rewire (run-mode / preseed); this passes the 3-arg request from
|
|
// existing State fields so the app keeps compiling.
|
|
var req = new ApplyRequest(
|
|
Flavour: State.Flavour!,
|
|
BitLockerPin: State.BitLockerPin,
|
|
Apps: apps);
|
|
|
|
var progress = new Progress<ApplyProgress>(p =>
|
|
{
|
|
_ = InvokeAsync(() =>
|
|
{
|
|
_percent = p.Percent;
|
|
_stageLabel = p.Stage;
|
|
StateHasChanged();
|
|
});
|
|
});
|
|
|
|
try
|
|
{
|
|
await ApplyService.RunAsync(req, progress);
|
|
_complete = true;
|
|
_running = false;
|
|
_percent = 100;
|
|
|
|
// Apply succeeded: wipe the BitLocker pin from the preconfig and stamp the
|
|
// configured marker so the next launch opens toolbox-home instead of re-applying.
|
|
PreconfigStore.ClearPin();
|
|
PreconfigStore.MarkConfigured();
|
|
|
|
StateHasChanged();
|
|
await OnRunningChanged.InvokeAsync(false);
|
|
await OnComplete.InvokeAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_running = false;
|
|
_errorMessage = ex.Message;
|
|
StateHasChanged();
|
|
await OnRunningChanged.InvokeAsync(false);
|
|
}
|
|
}
|
|
}
|