feat(transport-ui): import Map step + per-line diff view (M8 E2)
This commit is contained in:
@@ -206,6 +206,8 @@
|
||||
var hasBlockers = _preview.Items.Any(i => i.Kind == ConflictKind.Blocker);
|
||||
|
||||
<div>
|
||||
@RenderMapSection();
|
||||
|
||||
<p class="text-body-secondary">
|
||||
Review each artifact in the bundle and choose how it should be applied
|
||||
to this environment. Identical items are skipped automatically; new
|
||||
@@ -245,11 +247,22 @@
|
||||
</tr>
|
||||
@if (item.Kind == ConflictKind.Modified && !string.IsNullOrEmpty(item.FieldDiffJson))
|
||||
{
|
||||
var lineDiff = TryExtractLineDiff(item.FieldDiffJson);
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<details>
|
||||
<summary class="small">Field diff</summary>
|
||||
<pre class="small mb-0"><code>@item.FieldDiffJson</code></pre>
|
||||
@if (lineDiff is not null)
|
||||
{
|
||||
<div class="mt-2" data-testid="code-line-diff">
|
||||
<div class="small text-body-secondary mb-1">Code changes</div>
|
||||
<LineDiffView LineDiff="lineDiff" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<pre class="small mb-0"><code>@item.FieldDiffJson</code></pre>
|
||||
}
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -283,6 +296,123 @@
|
||||
</div>
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 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;
|
||||
}
|
||||
|
||||
<div class="card mb-4" data-testid="map-section">
|
||||
<div class="card-header bg-body-tertiary">
|
||||
<strong>Resolve site & connection references</strong>
|
||||
<span class="small text-body-secondary ms-2">
|
||||
This bundle references sites/connections from its source environment.
|
||||
Map each to an existing target, or create a new one.
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (hasSiteMappings)
|
||||
{
|
||||
<h6 class="small text-uppercase text-body-secondary">Sites</h6>
|
||||
<table class="table table-sm align-middle mb-4" data-testid="map-sites-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Source identifier</th>
|
||||
<th>Source name</th>
|
||||
<th>Map to target</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var rsm in _preview.RequiredSiteMappings)
|
||||
{
|
||||
var chosen = _siteChoices.TryGetValue(rsm.SourceSiteIdentifier, out var c) ? c : CreateNewValue;
|
||||
<tr data-testid="map-site-row">
|
||||
<td><code>@rsm.SourceSiteIdentifier</code></td>
|
||||
<td>@rsm.SourceSiteName</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm"
|
||||
style="max-width: 22rem;"
|
||||
data-testid="@($"map-site-select-{rsm.SourceSiteIdentifier}")"
|
||||
value="@chosen"
|
||||
@onchange="e => OnSiteChoiceChangedAsync(rsm.SourceSiteIdentifier, e.Value?.ToString())">
|
||||
<option value="@CreateNewValue" selected="@(string.IsNullOrEmpty(chosen))">Create new</option>
|
||||
@foreach (var site in _targetSites)
|
||||
{
|
||||
<option value="@site.SiteIdentifier" selected="@(chosen == site.SiteIdentifier)">
|
||||
@site.Name (@site.SiteIdentifier)
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (hasConnMappings)
|
||||
{
|
||||
<h6 class="small text-uppercase text-body-secondary">Connections</h6>
|
||||
@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);
|
||||
<div class="mb-3" data-testid="map-conn-group">
|
||||
<div class="small text-body-secondary mb-1">
|
||||
Site <code>@grp.Key</code>
|
||||
@if (string.IsNullOrEmpty(siteTarget))
|
||||
{
|
||||
<span class="badge bg-success ms-1">new site</span>
|
||||
}
|
||||
</div>
|
||||
<table class="table table-sm align-middle mb-0" data-testid="map-conns-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Source connection</th>
|
||||
<th>Map to target</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var rcm in grp)
|
||||
{
|
||||
var key = (rcm.SourceSiteIdentifier, rcm.SourceConnectionName);
|
||||
var chosenConn = _connectionChoices.TryGetValue(key, out var cc) ? cc : CreateNewValue;
|
||||
<tr data-testid="map-conn-row">
|
||||
<td>@rcm.SourceConnectionName</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm"
|
||||
style="max-width: 22rem;"
|
||||
data-testid="@($"map-conn-select-{rcm.SourceSiteIdentifier}-{rcm.SourceConnectionName}")"
|
||||
value="@chosenConn"
|
||||
@onchange="e => OnConnectionChoiceChanged(rcm.SourceSiteIdentifier, rcm.SourceConnectionName, e.Value?.ToString())">
|
||||
<option value="@CreateNewValue" selected="@(string.IsNullOrEmpty(chosenConn))">Create new</option>
|
||||
@foreach (var conn in targetConns)
|
||||
{
|
||||
<option value="@conn.Name" selected="@(chosenConn == conn.Name)">@conn.Name</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
private RenderFragment RenderKindBadge(ImportPreviewItem item) => __builder =>
|
||||
{
|
||||
var (cls, label) = item.Kind switch
|
||||
|
||||
Reference in New Issue
Block a user