database and gitsync issues fixed
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
@inject TenantService TenantService
|
||||
@inject DeploymentService DeploymentService
|
||||
@inject CnpgService CnpgService
|
||||
@inject MongoService MongoService
|
||||
@inject KubernetesOperationsService K8sOps
|
||||
@inject PrometheusService PrometheusService
|
||||
@inject AuditService AuditService
|
||||
@@ -110,20 +111,13 @@
|
||||
<small class="text-muted">Select which environments this app is deployed to.</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (environments is null)
|
||||
<LoadingPanel Loading="@(environments is null)">
|
||||
@if (environments is not null && environments.Count == 0)
|
||||
{
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
|
||||
</div>
|
||||
<EmptyState Icon="bi-layers"
|
||||
Message="No environments exist for this tenant yet. Create environments first." />
|
||||
}
|
||||
else if (environments.Count == 0)
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="bi bi-layers text-muted" style="font-size: 2rem;"></i>
|
||||
<p class="text-muted small mt-2 mb-0">No environments exist for this tenant yet. Create environments first.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
else if (environments is not null)
|
||||
{
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
|
||||
@foreach (Data.Environment env in environments)
|
||||
@@ -149,6 +143,7 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</LoadingPanel>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -183,8 +178,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-start gap-1">
|
||||
@GetSyncBadge(selectedDeployment.SyncStatus)
|
||||
@GetHealthBadge(selectedDeployment.HealthStatus)
|
||||
<button class="btn btn-link p-0 border-0 bg-transparent" title="View resources"
|
||||
@onclick="OpenResourcesTab">
|
||||
@GetSyncBadge(selectedDeployment.SyncStatus)
|
||||
</button>
|
||||
<button class="btn btn-link p-0 border-0 bg-transparent" title="View activity"
|
||||
@onclick="OpenActivityTab">
|
||||
@GetHealthBadge(selectedDeployment.HealthStatus)
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-outline-secondary ms-2"
|
||||
@onclick="StartEditDeployment" title="Edit deployment">
|
||||
@@ -436,20 +437,13 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (manifests is null)
|
||||
<LoadingPanel Loading="@(manifests is null)">
|
||||
@if (manifests is not null && manifests.Count == 0)
|
||||
{
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
|
||||
</div>
|
||||
<EmptyState Icon="bi-file-earmark-code"
|
||||
Message="No manifests yet. Add YAML manifests to define what gets deployed." />
|
||||
}
|
||||
else if (manifests.Count == 0)
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="bi bi-file-earmark-code text-muted" style="font-size: 2rem;"></i>
|
||||
<p class="text-muted small mt-2 mb-0">No manifests yet. Add YAML manifests to define what gets deployed.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
else if (manifests is not null)
|
||||
{
|
||||
@foreach (DeploymentManifest manifest in manifests)
|
||||
{
|
||||
@@ -469,12 +463,19 @@
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" @onclick="() => editingManifestId = null" disabled="@savingManifest">Cancel</button>
|
||||
}
|
||||
else if (confirmDeleteManifestId == manifest.Id)
|
||||
{
|
||||
<button class="btn btn-sm btn-danger" @onclick="() => DeleteManifest(manifest.Id)">
|
||||
<i class="bi bi-trash me-1"></i>Delete
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" @onclick="() => confirmDeleteManifestId = null">Cancel</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-sm btn-outline-primary" @onclick="() => StartEditManifest(manifest)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteManifest(manifest.Id)">
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => confirmDeleteManifestId = manifest.Id">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
}
|
||||
@@ -497,6 +498,7 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</LoadingPanel>
|
||||
|
||||
@if (yamlApplyOutput is not null)
|
||||
{
|
||||
@@ -621,18 +623,29 @@
|
||||
{
|
||||
<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>
|
||||
<h6 class="mb-3"><i class="bi bi-plus-circle me-1"></i>Attach a 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)
|
||||
@if (availableCnpgDatabases is not null && availableCnpgDatabases.Count > 0)
|
||||
{
|
||||
@foreach ((CnpgCluster cluster, CnpgDatabase database) in availableCnpgDatabases)
|
||||
{
|
||||
<option value="@database.Id">@cluster.Name / @database.Name</option>
|
||||
}
|
||||
<optgroup label="PostgreSQL (CNPG)">
|
||||
@foreach ((CnpgCluster cluster, CnpgDatabase database) in availableCnpgDatabases)
|
||||
{
|
||||
<option value="@database.Id">@cluster.Name / @database.Name</option>
|
||||
}
|
||||
</optgroup>
|
||||
}
|
||||
@if (availableMongoDatabases is not null && availableMongoDatabases.Count > 0)
|
||||
{
|
||||
<optgroup label="MongoDB">
|
||||
@foreach ((MongoCluster cluster, MongoDatabase database) in availableMongoDatabases)
|
||||
{
|
||||
<option value="@database.Id">@cluster.Name / @database.Name</option>
|
||||
}
|
||||
</optgroup>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@@ -655,21 +668,15 @@
|
||||
}
|
||||
|
||||
@* --- Binding list --- *@
|
||||
@if (databaseBindings is null)
|
||||
<LoadingPanel Loading="@(databaseBindings is null)">
|
||||
@if (databaseBindings is not null && databaseBindings.Count == 0)
|
||||
{
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
|
||||
</div>
|
||||
<EmptyState Icon="bi-database"
|
||||
Message="No databases attached. Attach a database to have its credentials automatically synced to this deployment's namespace." />
|
||||
}
|
||||
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
|
||||
else if (databaseBindings is not null)
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
@@ -700,16 +707,26 @@
|
||||
@(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>
|
||||
@if (confirmRemoveBindingId == binding.Id)
|
||||
{
|
||||
<button class="btn btn-sm btn-danger me-1" @onclick="() => RemoveDatabaseBinding(binding.Id)">Detach</button>
|
||||
<button class="btn btn-sm btn-link p-0" @onclick="() => confirmRemoveBindingId = null">Cancel</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-link btn-sm text-danger p-0" title="Detach"
|
||||
@onclick="() => confirmRemoveBindingId = binding.Id">
|
||||
<i class="bi bi-x-circle" style="font-size:0.85rem;"></i>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</LoadingPanel>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -770,21 +787,15 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (cacheBindings is null)
|
||||
<LoadingPanel Loading="@(cacheBindings is null)">
|
||||
@if (cacheBindings is not null && cacheBindings.Count == 0)
|
||||
{
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-warning" role="status"></div>
|
||||
</div>
|
||||
<EmptyState Icon="bi-lightning-charge"
|
||||
Message="No Redis clusters attached. Attach one to have REDIS_HOST, REDIS_PORT, and REDIS_PASSWORD synced into this deployment's namespace." />
|
||||
}
|
||||
else if (cacheBindings.Count == 0)
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="bi bi-lightning-charge text-muted" style="font-size: 2rem;"></i>
|
||||
<p class="text-muted small mt-2 mb-0">No Redis clusters attached. Attach one to have REDIS_HOST, REDIS_PORT, and REDIS_PASSWORD synced into this deployment's namespace.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
else if (cacheBindings is not null)
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
@@ -808,16 +819,26 @@
|
||||
@(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="() => RemoveCacheBinding(binding.Id)">
|
||||
<i class="bi bi-x-circle" style="font-size:0.85rem;"></i>
|
||||
</button>
|
||||
@if (confirmRemoveCacheBindingId == binding.Id)
|
||||
{
|
||||
<button class="btn btn-sm btn-danger me-1" @onclick="() => RemoveCacheBinding(binding.Id)">Detach</button>
|
||||
<button class="btn btn-sm btn-link p-0" @onclick="() => confirmRemoveCacheBindingId = null">Cancel</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-link btn-sm text-danger p-0" title="Detach"
|
||||
@onclick="() => confirmRemoveCacheBindingId = binding.Id">
|
||||
<i class="bi bi-x-circle" style="font-size:0.85rem;"></i>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</LoadingPanel>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1081,20 +1102,13 @@
|
||||
}
|
||||
|
||||
@* --- Deployment list --- *@
|
||||
@if (deployments is null)
|
||||
<LoadingPanel Loading="@(deployments is null)">
|
||||
@if (deployments is not null && deployments.Count == 0)
|
||||
{
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
|
||||
</div>
|
||||
<EmptyState Icon="bi-rocket-takeoff"
|
||||
Message="No deployments yet. Create one to start deploying to Kubernetes." />
|
||||
}
|
||||
else if (deployments.Count == 0)
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="bi bi-rocket-takeoff text-muted" style="font-size: 2rem;"></i>
|
||||
<p class="text-muted small mt-2 mb-0">No deployments yet. Create one to start deploying to Kubernetes.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
else if (deployments is not null)
|
||||
{
|
||||
<div class="row row-cols-1 row-cols-lg-2 g-2">
|
||||
@foreach (AppDeployment deploy in deployments)
|
||||
@@ -1129,6 +1143,7 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</LoadingPanel>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1320,10 +1335,12 @@
|
||||
// Database bindings
|
||||
private List<DatabaseBinding>? databaseBindings;
|
||||
private List<(CnpgCluster Cluster, CnpgDatabase Database)>? availableCnpgDatabases;
|
||||
private List<(MongoCluster Cluster, MongoDatabase Database)>? availableMongoDatabases;
|
||||
private bool showAddBinding;
|
||||
private Guid newBindingDatabaseId;
|
||||
private string newBindingSecretName = "";
|
||||
private string? bindingError;
|
||||
private Guid? confirmRemoveBindingId;
|
||||
|
||||
// Cache bindings
|
||||
private List<CacheBinding>? cacheBindings;
|
||||
@@ -1332,6 +1349,10 @@
|
||||
private Guid newCacheBindingClusterId;
|
||||
private string newCacheBindingSecretName = "redis-cache";
|
||||
private string? cacheBindingError;
|
||||
private Guid? confirmRemoveCacheBindingId;
|
||||
|
||||
// Manifest confirm delete
|
||||
private Guid? confirmDeleteManifestId;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -1629,6 +1650,7 @@
|
||||
|
||||
private async Task DeleteManifest(Guid manifestId)
|
||||
{
|
||||
confirmDeleteManifestId = null;
|
||||
await DeploymentService.DeleteManifestAsync(manifestId);
|
||||
await LoadManifests();
|
||||
}
|
||||
@@ -1761,6 +1783,15 @@
|
||||
.SelectMany(c => c.Databases.Select(d => (c, d)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (availableMongoDatabases is null)
|
||||
{
|
||||
List<MongoCluster> mongoClusters = await MongoService.GetClustersAsync(TenantId);
|
||||
availableMongoDatabases = mongoClusters
|
||||
.Where(c => !c.IsExternal)
|
||||
.SelectMany(c => c.Databases.Select(d => (c, d)))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1828,17 +1859,29 @@
|
||||
|
||||
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?
|
||||
(MongoCluster cluster, MongoDatabase _)? mongoMatch = availableMongoDatabases?
|
||||
.FirstOrDefault(x => x.Database.Id == newBindingDatabaseId);
|
||||
|
||||
if (match.HasValue)
|
||||
if (mongoMatch.HasValue)
|
||||
{
|
||||
await CnpgService.SyncDatabaseCredentialsToK8sAsync(
|
||||
TenantId, match.Value.cluster.Id, newBindingDatabaseId);
|
||||
await MongoService.AddMongoDatabaseBindingAsync(
|
||||
selectedDeployment.Id, newBindingDatabaseId, newBindingSecretName);
|
||||
await MongoService.SyncDatabaseCredentialsToK8sAsync(
|
||||
TenantId, mongoMatch.Value.cluster.Id, newBindingDatabaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CnpgService.AddCnpgDatabaseBindingAsync(
|
||||
selectedDeployment.Id, newBindingDatabaseId, newBindingSecretName);
|
||||
|
||||
(CnpgCluster cluster, CnpgDatabase _)? cnpgMatch = availableCnpgDatabases?
|
||||
.FirstOrDefault(x => x.Database.Id == newBindingDatabaseId);
|
||||
|
||||
if (cnpgMatch.HasValue)
|
||||
{
|
||||
await CnpgService.SyncDatabaseCredentialsToK8sAsync(
|
||||
TenantId, cnpgMatch.Value.cluster.Id, newBindingDatabaseId);
|
||||
}
|
||||
}
|
||||
|
||||
showAddBinding = false;
|
||||
@@ -1854,6 +1897,7 @@
|
||||
|
||||
private async Task RemoveDatabaseBinding(Guid bindingId)
|
||||
{
|
||||
confirmRemoveBindingId = null;
|
||||
bindingError = null;
|
||||
|
||||
try
|
||||
@@ -1909,6 +1953,7 @@
|
||||
|
||||
private async Task RemoveCacheBinding(Guid bindingId)
|
||||
{
|
||||
confirmRemoveCacheBindingId = null;
|
||||
cacheBindingError = null;
|
||||
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user