refactor: simplify data connections from many-to-many site assignment to direct site ownership
Replace SiteDataConnectionAssignment join table with a direct SiteId FK on DataConnection, simplifying the data model, repositories, UI, CLI, and deployment service.
This commit is contained in:
@@ -21,6 +21,26 @@
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
@if (Id.HasValue)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Site</label>
|
||||
<input type="text" class="form-control form-control-sm" value="@(_siteName)" disabled />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Site</label>
|
||||
<select class="form-select form-select-sm" @bind="_formSiteId">
|
||||
<option value="0">Select site...</option>
|
||||
@foreach (var site in _sites)
|
||||
{
|
||||
<option value="@site.Id">@site.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Name</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="_formName" />
|
||||
@@ -57,6 +77,9 @@
|
||||
|
||||
private bool _loading = true;
|
||||
private DataConnection? _editingConnection;
|
||||
private List<Site> _sites = new();
|
||||
private int _formSiteId;
|
||||
private string _siteName = string.Empty;
|
||||
private string _formName = string.Empty;
|
||||
private string _formProtocol = string.Empty;
|
||||
private string? _formConfiguration;
|
||||
@@ -64,6 +87,8 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
||||
|
||||
if (Id.HasValue)
|
||||
{
|
||||
try
|
||||
@@ -71,6 +96,8 @@
|
||||
_editingConnection = await SiteRepository.GetDataConnectionByIdAsync(Id.Value);
|
||||
if (_editingConnection != null)
|
||||
{
|
||||
_formSiteId = _editingConnection.SiteId;
|
||||
_siteName = _sites.FirstOrDefault(s => s.Id == _formSiteId)?.Name ?? $"Site {_formSiteId}";
|
||||
_formName = _editingConnection.Name;
|
||||
_formProtocol = _editingConnection.Protocol;
|
||||
_formConfiguration = _editingConnection.Configuration;
|
||||
@@ -87,6 +114,7 @@
|
||||
private async Task SaveConnection()
|
||||
{
|
||||
_formError = null;
|
||||
if (_formSiteId == 0) { _formError = "Site is required."; return; }
|
||||
if (string.IsNullOrWhiteSpace(_formName)) { _formError = "Name is required."; return; }
|
||||
if (string.IsNullOrWhiteSpace(_formProtocol)) { _formError = "Protocol is required."; return; }
|
||||
|
||||
@@ -101,7 +129,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
var conn = new DataConnection(_formName.Trim(), _formProtocol)
|
||||
var conn = new DataConnection(_formName.Trim(), _formProtocol, _formSiteId)
|
||||
{
|
||||
Configuration = _formConfiguration?.Trim()
|
||||
};
|
||||
|
||||
@@ -25,58 +25,14 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Assignment form *@
|
||||
@if (_showAssignForm)
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Assign Connection to Site</h6>
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small">Connection</label>
|
||||
<select class="form-select form-select-sm" @bind="_assignConnectionId">
|
||||
<option value="0">Select connection...</option>
|
||||
@foreach (var conn in _connections)
|
||||
{
|
||||
<option value="@conn.Id">@conn.Name (@conn.Protocol)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small">Site</label>
|
||||
<select class="form-select form-select-sm" @bind="_assignSiteId">
|
||||
<option value="0">Select site...</option>
|
||||
@foreach (var site in _sites)
|
||||
{
|
||||
<option value="@site.Id">@site.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-success btn-sm me-1" @onclick="SaveAssignment">Assign</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelAssignForm">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (_assignError != null)
|
||||
{
|
||||
<div class="text-danger small mt-1">@_assignError</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-outline-info btn-sm" @onclick="ShowAssignForm">Assign to Site</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Protocol</th>
|
||||
<th>Site</th>
|
||||
<th>Configuration</th>
|
||||
<th>Assigned Sites</th>
|
||||
<th style="width: 160px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -93,29 +49,8 @@
|
||||
<td>@conn.Id</td>
|
||||
<td>@conn.Name</td>
|
||||
<td><span class="badge bg-secondary">@conn.Protocol</span></td>
|
||||
<td>@(_siteLookup.GetValueOrDefault(conn.SiteId)?.Name ?? $"Site {conn.SiteId}")</td>
|
||||
<td class="text-muted small text-truncate" style="max-width: 300px;">@(conn.Configuration ?? "—")</td>
|
||||
<td>
|
||||
@{
|
||||
var assignedSites = _connectionSites.GetValueOrDefault(conn.Id);
|
||||
}
|
||||
@if (assignedSites != null && assignedSites.Count > 0)
|
||||
{
|
||||
@foreach (var assignment in assignedSites)
|
||||
{
|
||||
var siteName = _sites.FirstOrDefault(s => s.Id == assignment.SiteId)?.Name ?? $"Site {assignment.SiteId}";
|
||||
<span class="badge bg-info text-dark me-1">
|
||||
@siteName
|
||||
<button type="button" class="btn-close btn-close-white ms-1"
|
||||
style="font-size: 0.5rem;"
|
||||
@onclick="() => RemoveAssignment(assignment)"></button>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted small">None</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-outline-primary btn-sm py-0 px-1 me-1"
|
||||
@onclick='() => NavigationManager.NavigateTo($"/admin/data-connections/{conn.Id}/edit")'>Edit</button>
|
||||
@@ -131,16 +66,10 @@
|
||||
|
||||
@code {
|
||||
private List<DataConnection> _connections = new();
|
||||
private List<Site> _sites = new();
|
||||
private Dictionary<int, List<SiteDataConnectionAssignment>> _connectionSites = new();
|
||||
private Dictionary<int, Site> _siteLookup = new();
|
||||
private bool _loading = true;
|
||||
private string? _errorMessage;
|
||||
|
||||
private bool _showAssignForm;
|
||||
private int _assignConnectionId;
|
||||
private int _assignSiteId;
|
||||
private string? _assignError;
|
||||
|
||||
private ToastNotification _toast = default!;
|
||||
private ConfirmDialog _confirmDialog = default!;
|
||||
|
||||
@@ -155,24 +84,9 @@
|
||||
_errorMessage = null;
|
||||
try
|
||||
{
|
||||
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
||||
var sites = await SiteRepository.GetAllSitesAsync();
|
||||
_siteLookup = sites.ToDictionary(s => s.Id);
|
||||
_connections = (await SiteRepository.GetAllDataConnectionsAsync()).ToList();
|
||||
|
||||
// Load site assignments for each connection
|
||||
_connectionSites.Clear();
|
||||
foreach (var site in _sites)
|
||||
{
|
||||
var siteConns = await SiteRepository.GetDataConnectionsBySiteIdAsync(site.Id);
|
||||
foreach (var conn in siteConns)
|
||||
{
|
||||
if (!_connectionSites.ContainsKey(conn.Id))
|
||||
_connectionSites[conn.Id] = new List<SiteDataConnectionAssignment>();
|
||||
|
||||
var assignment = await SiteRepository.GetSiteDataConnectionAssignmentAsync(site.Id, conn.Id);
|
||||
if (assignment != null)
|
||||
_connectionSites[conn.Id].Add(assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -199,58 +113,4 @@
|
||||
_toast.ShowError($"Delete failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowAssignForm()
|
||||
{
|
||||
_assignConnectionId = 0;
|
||||
_assignSiteId = 0;
|
||||
_assignError = null;
|
||||
_showAssignForm = true;
|
||||
}
|
||||
|
||||
private void CancelAssignForm()
|
||||
{
|
||||
_showAssignForm = false;
|
||||
_assignError = null;
|
||||
}
|
||||
|
||||
private async Task SaveAssignment()
|
||||
{
|
||||
_assignError = null;
|
||||
if (_assignConnectionId == 0) { _assignError = "Select a connection."; return; }
|
||||
if (_assignSiteId == 0) { _assignError = "Select a site."; return; }
|
||||
|
||||
try
|
||||
{
|
||||
var assignment = new SiteDataConnectionAssignment
|
||||
{
|
||||
SiteId = _assignSiteId,
|
||||
DataConnectionId = _assignConnectionId
|
||||
};
|
||||
await SiteRepository.AddSiteDataConnectionAssignmentAsync(assignment);
|
||||
await SiteRepository.SaveChangesAsync();
|
||||
_showAssignForm = false;
|
||||
_toast.ShowSuccess("Connection assigned to site.");
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_assignError = $"Assignment failed: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveAssignment(SiteDataConnectionAssignment assignment)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SiteRepository.DeleteSiteDataConnectionAssignmentAsync(assignment.Id);
|
||||
await SiteRepository.SaveChangesAsync();
|
||||
_toast.ShowSuccess("Assignment removed.");
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_toast.ShowError($"Remove failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,10 +178,9 @@
|
||||
_deploying = true;
|
||||
try
|
||||
{
|
||||
var command = await ArtifactDeploymentService.BuildDeployArtifactsCommandAsync();
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await ArtifactDeploymentService.RetryForSiteAsync(
|
||||
site.SiteIdentifier, command, user);
|
||||
site.Id, site.SiteIdentifier, user);
|
||||
|
||||
if (result.IsSuccess)
|
||||
_toast.ShowSuccess($"Artifacts deployed to '{site.Name}'.");
|
||||
@@ -203,9 +202,8 @@
|
||||
_deploying = true;
|
||||
try
|
||||
{
|
||||
var command = await ArtifactDeploymentService.BuildDeployArtifactsCommandAsync();
|
||||
var user = await GetCurrentUserAsync();
|
||||
var result = await ArtifactDeploymentService.DeployToAllSitesAsync(command, user);
|
||||
var result = await ArtifactDeploymentService.DeployToAllSitesAsync(user);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
|
||||
@@ -697,13 +697,8 @@
|
||||
var attrs = await TemplateEngineRepository.GetAttributesByTemplateIdAsync(inst.TemplateId);
|
||||
_bindingDataSourceAttrs = attrs.Where(a => !string.IsNullOrEmpty(a.DataSourceReference)).ToList();
|
||||
|
||||
// Load data connections for this site
|
||||
// Load data connections for this site (each connection now belongs to exactly one site)
|
||||
_siteConnections = (await SiteRepository.GetDataConnectionsBySiteIdAsync(inst.SiteId)).ToList();
|
||||
if (_siteConnections.Count == 0)
|
||||
{
|
||||
// Also show unassigned connections (they may not be assigned to a site yet)
|
||||
_siteConnections = (await SiteRepository.GetAllDataConnectionsAsync()).ToList();
|
||||
}
|
||||
|
||||
// Load existing bindings
|
||||
var existingBindings = await TemplateEngineRepository.GetBindingsByInstanceIdAsync(inst.Id);
|
||||
|
||||
Reference in New Issue
Block a user