}
@switch (_step)
{
case ImportWizardStep.Upload:
@RenderStepUpload();
break;
case ImportWizardStep.Passphrase:
@RenderStepPassphrase();
break;
case ImportWizardStep.Diff:
@RenderStepDiff();
break;
case ImportWizardStep.Confirm:
@RenderStepConfirm();
break;
case ImportWizardStep.Result:
@RenderStepResult();
break;
}
@* No lede — the step pills above and the InputFile's own accept filter +
size hint already say what belongs here. *@
Maximum bundle size: @Options.Value.MaxBundleSizeMb MB.
@if (_uploadInProgress)
{
Reading bundle…
}
@if (_bundleTempPath is not null && _errorMessage is null)
{
@if (_session is not null)
{
Source environment
@_session.Manifest.SourceEnvironment
Exported by
@_session.Manifest.ExportedBy
Created
@_session.Manifest.CreatedAtUtc.ToString("u")
Content count
@_session.Manifest.Contents.Count items
SHA-256
@_session.Manifest.ContentHash
Signature
@switch (_session.SignatureStatus)
{
case BundleSignatureStatus.Verified:
Verified — signed by @_session.SignerName
break;
case BundleSignatureStatus.UntrustedKey:
Signed by an untrusted key@_session.SignerKeyId
break;
default:
Unsigned
break;
}
This bundle is encrypted. Enter the passphrase that was used to
produce it. You have @attemptsLeft of @maxAttempts attempts before
the upload must be restarted.
@if (_failedUnlockAttempts > 0)
{
Failed unlock attempts: @_failedUnlockAttempts of @maxAttempts.
}
};
// ============================================================
// Step 3 — Diff & resolve conflicts
// ============================================================
private RenderFragment RenderStepDiff() => __builder =>
{
if (_preview is null || _resolutions is null)
{
No preview available — please go back and re-upload.
return;
}
var (adds, overs, skips, renames, blockers) = CountResolutions();
var hasBlockers = _preview.Items.Any(i => i.Kind == ConflictKind.Blocker);
@RenderMapSection();
@* Per-kind resolution defaults are documented in Component-Transport.md
§Diff classification; the Status badge shows each row's kind in place. *@
Apply to all modified:
Type
Name
Status
Existing
Incoming
Action
@foreach (var item in _preview.Items)
{
var key = (item.EntityType, item.Name);
var current = _resolutions[key];
@item.EntityType
@* Bundle-supplied (external) name — clip so it can't set the
table's width. .cell-clip is display:block, hence the span. *@
}
else
{
@* Raw JSON from an externally produced bundle —
wrap + cap the height rather than forcing the
already-scroll-boxed table to scroll sideways. *@
};
// ============================================================
// Step 3 — Map sub-section (M8 E2)
// ============================================================
// Shown only when the preview references source-environment sites/connections
// the operator must resolve before import. For central-config-only bundles the
// preview carries no required mappings and this renders nothing.
private RenderFragment RenderMapSection() => __builder =>
{
if (_preview is null) return;
var hasSiteMappings = _preview.RequiredSiteMappings.Count > 0;
var hasConnMappings = _preview.RequiredConnectionMappings.Count > 0;
if (!hasSiteMappings && !hasConnMappings)
{
return;
}
@* The Source identifier / Source name / Map to target headings and the
"Create new" option below carry the explanation this header used to. *@
Resolve site & connection references
@if (hasSiteMappings)
{
Sites
Source identifier
Source name
Map to target
@foreach (var rsm in _preview.RequiredSiteMappings)
{
var chosen = _siteChoices.TryGetValue(rsm.SourceSiteIdentifier, out var c) ? c : CreateNewValue;
@rsm.SourceSiteIdentifier
@rsm.SourceSiteName
}
}
@if (hasConnMappings)
{
Connections
@foreach (var grp in _preview.RequiredConnectionMappings.GroupBy(m => m.SourceSiteIdentifier))
{
var siteTarget = _siteChoices.TryGetValue(grp.Key, out var st) ? st : CreateNewValue;
var targetConns = ConnectionsForChosenTarget(grp.Key);
Site @grp.Key
@if (string.IsNullOrEmpty(siteTarget))
{
new site
}
Source connection
Map to target
@foreach (var rcm in grp)
{
var key = (rcm.SourceSiteIdentifier, rcm.SourceConnectionName);
var chosenConn = _connectionChoices.TryGetValue(key, out var cc) ? cc : CreateNewValue;
@rcm.SourceConnectionName
}
}
}
};
private RenderFragment RenderKindBadge(ImportPreviewItem item) => __builder =>
{
var (cls, label) = item.Kind switch
{
ConflictKind.Identical => ("bg-secondary", "Identical"),
ConflictKind.Modified => ("bg-warning text-dark", "Modified"),
ConflictKind.New => ("bg-success", "New"),
ConflictKind.Blocker => ("bg-danger", "Blocker"),
ConflictKind.Warning => ("bg-warning text-dark", "Warning"),
_ => ("bg-light text-dark", item.Kind.ToString()),
};
@label
};
private RenderFragment RenderResolutionControls(ImportPreviewItem item, ImportResolution current) => __builder =>
{
// Identical → forced Skip; New → forced Add; Blocker → no actions.
if (item.Kind == ConflictKind.Identical)
{
Skip
return;
}
if (item.Kind == ConflictKind.New)
{
Add
return;
}
if (item.Kind == ConflictKind.Blocker)
{
—
return;
}
// Warning items are advisory "Reference" rows, not importable entities —
// no resolution to choose (the import proceeds regardless).
if (item.Kind == ConflictKind.Warning)
{
—
return;
}
var key = (item.EntityType, item.Name);
@foreach (var action in new[] { ResolutionAction.Overwrite, ResolutionAction.Skip, ResolutionAction.Rename })
{
var inputId = $"res-{item.EntityType}-{item.Name}-{action}";