@using EntKube.Web.Data @using EntKube.Web.Services @using Microsoft.AspNetCore.Components.Forms @using Microsoft.EntityFrameworkCore @inject TenantService TenantService @inject VaultService VaultService @* =========================================================================== Three-level drill-down: Environments ▸ Clusters ▸ Cluster Detail The user sees ONE level at a time — no nested accordion clutter. =========================================================================== *@ @if (selectedCluster is not null) { @* ───────────── Level 3: Cluster Detail ───────────── *@ } else if (selectedEnv is not null) { @* ───────────── Level 2: Clusters in an environment ───────────── *@ @* Breadcrumb back to environments *@

@selectedEnv.Name

Clusters registered in this environment
@* --- Add Cluster (kubeconfig) --- *@ @if (showAddCluster) {
Register a Cluster
@if (parsedContexts is null || parsedContexts.Count == 0) {

Paste your kubeconfig YAML or upload a file to auto-detect clusters and API server URLs.

} else {

Found @parsedContexts.Count context(s). Select one to register:

@foreach (KubeconfigContext ctx in parsedContexts) { }
} @if (!string.IsNullOrEmpty(clusterError)) {
@clusterError
}
} else { } @* --- Cluster Cards --- *@ @if (envClusters is not null && envClusters.Count == 0) { } else if (envClusters is not null) {
@foreach (KubernetesCluster cluster in envClusters) {
@cluster.Name
@if (!string.IsNullOrEmpty(cluster.ContextName)) {
@cluster.ContextName
} @cluster.ApiServerUrl
}
}
} else { @* ───────────── Level 1: Environment list ───────────── *@

Environments

Deployment stages for this tenant. Click an environment to manage its clusters.

@if (!string.IsNullOrEmpty(errorMessage)) { } @if (environments is not null && environments.Count == 0) { } else if (environments is not null) {
@foreach (Data.Environment env in environments) {
@if (confirmDeleteEnvId == env.Id) {
Delete @env.Name and all its clusters?
} else {
@env.Name
@(clusterCounts.GetValueOrDefault(env.Id, 0)) cluster(s)
}
}
}
} @code { [Parameter] public Guid TenantId { get; set; } // --- Level 1: Environment list --- private List? environments; private Dictionary clusterCounts = new(); private string newName = ""; private string? errorMessage; private Guid? confirmDeleteEnvId; // --- Level 2: Selected environment → clusters --- private Data.Environment? selectedEnv; private List? envClusters; private bool showAddCluster; // --- Level 3: Selected cluster → detail --- private KubernetesCluster? selectedCluster; // --- Kubeconfig parsing --- private string kubeconfigInput = ""; private List? parsedContexts; private string? selectedContext; private string? selectedServerUrl; private string newClusterName = ""; private string? clusterError; protected override async Task OnInitializedAsync() { await LoadEnvironments(); } // ──────────────── Level 1 ──────────────── private async Task LoadEnvironments() { environments = await TenantService.GetEnvironmentsAsync(TenantId); // Pre-load cluster counts so the list shows "N cluster(s)" without expanding. List allClusters = await TenantService.GetClustersAsync(TenantId); clusterCounts = allClusters.GroupBy(c => c.EnvironmentId).ToDictionary(g => g.Key, g => g.Count()); } private async Task Create() { errorMessage = null; try { await TenantService.CreateEnvironmentAsync(TenantId, newName.Trim()); newName = ""; await LoadEnvironments(); } catch (DbUpdateException) { errorMessage = "An environment with that name already exists."; } } private async Task Delete(Guid id) { confirmDeleteEnvId = null; await TenantService.DeleteEnvironmentAsync(id); await LoadEnvironments(); } private async Task OpenEnvironment(Data.Environment env) { selectedEnv = env; selectedCluster = null; showAddCluster = false; ResetClusterForm(); await LoadClusters(); } private async Task BackToEnvironments() { selectedEnv = null; selectedCluster = null; envClusters = null; await LoadEnvironments(); } // ──────────────── Level 2 ──────────────── private async Task LoadClusters() { List allClusters = await TenantService.GetClustersAsync(TenantId); envClusters = allClusters.Where(c => c.EnvironmentId == selectedEnv!.Id).ToList(); } private void OpenCluster(KubernetesCluster cluster) { selectedCluster = cluster; } private async Task BackToClusters() { selectedCluster = null; await LoadClusters(); } private async Task ClusterDeleted() { selectedCluster = null; await LoadClusters(); } // --- Kubeconfig --- private void ParseKubeconfig() { clusterError = null; parsedContexts = KubeconfigParser.ParseContexts(kubeconfigInput); if (parsedContexts.Count == 0) { clusterError = "No valid contexts found in the kubeconfig. Check the YAML format."; return; } KubeconfigContext? defaultCtx = parsedContexts.FirstOrDefault(c => c.IsCurrent) ?? parsedContexts.First(); SelectContext(defaultCtx); } private async Task HandleFileUpload(InputFileChangeEventArgs e) { IBrowserFile file = e.File; if (file.Size > 1_048_576) { clusterError = "File too large. Kubeconfig files should be under 1 MB."; return; } using StreamReader reader = new(file.OpenReadStream(maxAllowedSize: 1_048_576)); kubeconfigInput = await reader.ReadToEndAsync(); ParseKubeconfig(); } private void SelectContext(KubeconfigContext ctx) { selectedContext = ctx.Name; selectedServerUrl = ctx.ClusterServer; if (string.IsNullOrWhiteSpace(newClusterName)) { newClusterName = ctx.Name; } } private void ResetClusterForm() { kubeconfigInput = ""; parsedContexts = null; selectedContext = null; selectedServerUrl = null; newClusterName = ""; clusterError = null; } private async Task CreateCluster() { clusterError = null; if (string.IsNullOrWhiteSpace(selectedServerUrl)) { clusterError = "No context selected."; return; } try { await TenantService.CreateClusterAsync( TenantId, selectedEnv!.Id, newClusterName.Trim(), selectedServerUrl, contextName: selectedContext, kubeconfig: kubeconfigInput); showAddCluster = false; ResetClusterForm(); await LoadClusters(); } catch (DbUpdateException) { clusterError = "A cluster with that name already exists."; } } }