gateway api fixes
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject TenantService TenantService
|
||||
@inject DeploymentService DeploymentService
|
||||
@inject CnpgService CnpgService
|
||||
|
||||
@* ═══════════════════════════════════════════════════════════════════
|
||||
App Detail — a full-page view for a single application.
|
||||
@@ -226,6 +227,16 @@
|
||||
<i class="bi bi-diagram-3 me-1"></i>Resources
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link @(deploymentSection == "databases" ? "active" : "")"
|
||||
@onclick="OpenDatabasesTab">
|
||||
<i class="bi bi-database me-1"></i>Databases
|
||||
@if (databaseBindings is not null && databaseBindings.Count > 0)
|
||||
{
|
||||
<span class="badge bg-primary ms-1">@databaseBindings.Count</span>
|
||||
}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@* ── Manifests sub-tab ── *@
|
||||
@@ -393,6 +404,123 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@* ── Databases sub-tab ── *@
|
||||
@if (deploymentSection == "databases")
|
||||
{
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white py-2 d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<i class="bi bi-database me-2 text-primary"></i>
|
||||
<strong>Database Bindings</strong>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-primary" @onclick="() => showAddBinding = !showAddBinding">
|
||||
<i class="bi bi-plus me-1"></i>Attach Database
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (!string.IsNullOrEmpty(bindingError))
|
||||
{
|
||||
<div class="alert alert-danger py-1 small mb-2">@bindingError</div>
|
||||
}
|
||||
|
||||
@* --- Add binding form --- *@
|
||||
@if (showAddBinding)
|
||||
{
|
||||
<div class="card border-primary mb-3">
|
||||
<div class="card-body">
|
||||
<h6 class="mb-3"><i class="bi bi-plus-circle me-1"></i>Attach a PostgreSQL Database</h6>
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small">Database</label>
|
||||
<select class="form-select form-select-sm" @bind="newBindingDatabaseId">
|
||||
<option value="">Select database...</option>
|
||||
@if (availableCnpgDatabases is not null)
|
||||
{
|
||||
@foreach ((CnpgCluster cluster, CnpgDatabase database) in availableCnpgDatabases)
|
||||
{
|
||||
<option value="@database.Id">@cluster.Name / @database.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small">Secret name in app namespace</label>
|
||||
<input class="form-control form-control-sm" @bind="newBindingSecretName"
|
||||
placeholder="e.g. my-app-db" />
|
||||
<div class="form-text">The K8s Secret created in <code>@selectedDeployment?.Namespace</code>.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-sm btn-primary" @onclick="AddDatabaseBinding"
|
||||
disabled="@(newBindingDatabaseId == Guid.Empty || string.IsNullOrWhiteSpace(newBindingSecretName))">
|
||||
Attach & Sync
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" @onclick="() => showAddBinding = false">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@* --- Binding list --- *@
|
||||
@if (databaseBindings is null)
|
||||
{
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
|
||||
</div>
|
||||
}
|
||||
else if (databaseBindings.Count == 0)
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="bi bi-database text-muted" style="font-size: 2rem;"></i>
|
||||
<p class="text-muted small mt-2 mb-0">No databases attached. Attach a database to have its credentials automatically synced to this deployment's namespace.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="small">Database</th>
|
||||
<th class="small">Secret name</th>
|
||||
<th class="small">Last synced</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (DatabaseBinding binding in databaseBindings)
|
||||
{
|
||||
<tr>
|
||||
<td class="small">
|
||||
@if (binding.CnpgDatabase is not null)
|
||||
{
|
||||
<i class="bi bi-database me-1 text-primary"></i>
|
||||
@binding.CnpgDatabase.CnpgCluster.Name<span class="text-muted"> / </span>@binding.CnpgDatabase.Name
|
||||
}
|
||||
else if (binding.MongoDatabase is not null)
|
||||
{
|
||||
<i class="bi bi-database me-1 text-success"></i>
|
||||
@binding.MongoDatabase.MongoCluster.Name<span class="text-muted"> / </span>@binding.MongoDatabase.Name
|
||||
}
|
||||
</td>
|
||||
<td><code class="small">@binding.KubernetesSecretName</code></td>
|
||||
<td class="small text-muted">
|
||||
@(binding.LastSyncedAt.HasValue ? binding.LastSyncedAt.Value.ToString("yyyy-MM-dd HH:mm") : "Never")
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-link btn-sm text-danger p-0" title="Detach"
|
||||
@onclick="() => RemoveDatabaseBinding(binding.Id)">
|
||||
<i class="bi bi-x-circle" style="font-size:0.85rem;"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -602,6 +730,14 @@
|
||||
// Resource tree
|
||||
private List<DeploymentResource>? resourceTree;
|
||||
|
||||
// Database bindings
|
||||
private List<DatabaseBinding>? databaseBindings;
|
||||
private List<(CnpgCluster Cluster, CnpgDatabase Database)>? availableCnpgDatabases;
|
||||
private bool showAddBinding;
|
||||
private Guid newBindingDatabaseId;
|
||||
private string newBindingSecretName = "";
|
||||
private string? bindingError;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadEnvironments();
|
||||
@@ -790,6 +926,80 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ──────── Database bindings ────────
|
||||
|
||||
private async Task OpenDatabasesTab()
|
||||
{
|
||||
deploymentSection = "databases";
|
||||
|
||||
if (selectedDeployment is not null)
|
||||
{
|
||||
databaseBindings = await CnpgService.GetDatabaseBindingsAsync(selectedDeployment.Id);
|
||||
|
||||
if (availableCnpgDatabases is null)
|
||||
{
|
||||
List<CnpgCluster> clusters = await CnpgService.GetClustersAsync(TenantId);
|
||||
availableCnpgDatabases = clusters
|
||||
.SelectMany(c => c.Databases.Select(d => (c, d)))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddDatabaseBinding()
|
||||
{
|
||||
if (selectedDeployment is null || newBindingDatabaseId == Guid.Empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bindingError = null;
|
||||
|
||||
try
|
||||
{
|
||||
DatabaseBinding binding = await CnpgService.AddCnpgDatabaseBindingAsync(
|
||||
selectedDeployment.Id, newBindingDatabaseId, newBindingSecretName);
|
||||
|
||||
// Find the cluster that owns this database so we can call sync.
|
||||
(CnpgCluster cluster, CnpgDatabase _)? match = availableCnpgDatabases?
|
||||
.FirstOrDefault(x => x.Database.Id == newBindingDatabaseId);
|
||||
|
||||
if (match.HasValue)
|
||||
{
|
||||
await CnpgService.SyncDatabaseCredentialsToK8sAsync(
|
||||
TenantId, match.Value.cluster.Id, newBindingDatabaseId);
|
||||
}
|
||||
|
||||
showAddBinding = false;
|
||||
newBindingDatabaseId = Guid.Empty;
|
||||
newBindingSecretName = "";
|
||||
databaseBindings = await CnpgService.GetDatabaseBindingsAsync(selectedDeployment.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
bindingError = $"Failed to attach database: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveDatabaseBinding(Guid bindingId)
|
||||
{
|
||||
bindingError = null;
|
||||
|
||||
try
|
||||
{
|
||||
await CnpgService.RemoveDatabaseBindingAsync(bindingId);
|
||||
|
||||
if (selectedDeployment is not null)
|
||||
{
|
||||
databaseBindings = await CnpgService.GetDatabaseBindingsAsync(selectedDeployment.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
bindingError = $"Failed to detach database: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
// ──────── Rendering helpers ────────
|
||||
|
||||
private RenderFragment GetTypeBadge(DeploymentType type) => type switch
|
||||
|
||||
Reference in New Issue
Block a user