@using EntKube.Web.Data @using EntKube.Web.Services @using Microsoft.EntityFrameworkCore @inject TenantService TenantService @inject DeploymentService DeploymentService @* ═══════════════════════════════════════════════════════════════════ App Detail — a full-page view for a single application. Shows app info, environment links, and is the extension point for future app-level features (config, deployments, secrets, etc.). ═══════════════════════════════════════════════════════════════════ *@ @* --- Back button --- *@ @* --- App header --- *@

@App.Name

@CustomerName Created @App.CreatedAt.ToString("MMM d, yyyy")
@if (!confirmDelete) { }
@if (confirmDelete) {
Permanently delete @App.Name and all its data?
}
@* --- App sub-sections (tabs for future expansion) --- *@ @if (section == "environments") {
Environment Links
Select which environments this app is deployed to.
@if (environments is null) {
} else if (environments.Count == 0) {

No environments exist for this tenant yet. Create environments first.

} else {
@foreach (Data.Environment env in environments) { bool isLinked = linkedEnvIds.Contains(env.Id);
@env.Name
@if (isLinked) { Active }
}
}
} @* ═══════════════════════════════════════════════════════════════════ Deployments Tab — manage deployments (Manual, YAML, Helm) targeting specific clusters. Shows ArgoCD-style sync/health status. ═══════════════════════════════════════════════════════════════════ *@ @if (section == "deployments") { @* --- Deployment detail view (drill-down into a single deployment) --- *@ @if (selectedDeployment is not null) {
@selectedDeployment.Name
@GetTypeBadge(selectedDeployment.Type) @selectedDeployment.Environment?.Name @selectedDeployment.Cluster?.Name @selectedDeployment.Namespace
@GetSyncBadge(selectedDeployment.SyncStatus) @GetHealthBadge(selectedDeployment.HealthStatus) @if (!confirmDeleteDeployment) { }
@if (confirmDeleteDeployment) {
Permanently delete @selectedDeployment.Name and all its manifests and resources?
} @if (selectedDeployment.Type == DeploymentType.HelmChart) {
@selectedDeployment.HelmChartName @selectedDeployment.HelmChartVersion from @selectedDeployment.HelmRepoUrl
}
@* --- Sub-tabs: Manifests / Resources / Helm Values --- *@ @* ── Manifests sub-tab ── *@ @if (deploymentSection == "manifests") {
Manifests
@if (showAddManifest) {
Add YAML Manifest
} @if (manifests is null) {
} else if (manifests.Count == 0) {

No manifests yet. Add YAML manifests to define what gets deployed.

} else { @foreach (DeploymentManifest manifest in manifests) {
@manifest.Kind @manifest.Name #@manifest.SortOrder
@if (editingManifestId == manifest.Id) { } else { }
@if (editingManifestId == manifest.Id) {
} else {
@manifest.YamlContent
}
} }
} @* ── Helm Values sub-tab ── *@ @if (deploymentSection == "helm-values") {
Helm Values

Override the chart's default values (YAML format).

} @* ── Resources sub-tab (ArgoCD-style tree) ── *@ @if (deploymentSection == "resources") {
Resource Tree
@if (resourceTree is null) {
} else if (resourceTree.Count == 0) {

No tracked resources yet. Resources appear here after the deployment syncs to the cluster.

} else { @foreach (DeploymentResource root in resourceTree) {
@RenderResource(root, 0)
} }
} } else { @* --- Deployment list view --- *@
Deployments
@* --- Create deployment form --- *@ @if (showCreateDeployment) {
Create Deployment
@if (newDeployType == DeploymentType.HelmChart) {
} @if (!string.IsNullOrEmpty(createDeployError)) {
@createDeployError
}
} @* --- Deployment list --- *@ @if (deployments is null) {
} else if (deployments.Count == 0) {

No deployments yet. Create one to start deploying to Kubernetes.

} else {
@foreach (AppDeployment deploy in deployments) {
@deploy.Name
@GetSyncBadge(deploy.SyncStatus) @GetHealthBadge(deploy.HealthStatus)
@GetTypeBadge(deploy.Type) @deploy.Environment?.Name @deploy.Cluster?.Name @deploy.Namespace
@if (!string.IsNullOrEmpty(deploy.StatusMessage)) {
@deploy.StatusMessage
}
}
}
} } @code { [Parameter] public required Data.App App { get; set; } [Parameter] public Guid TenantId { get; set; } [Parameter] public string CustomerName { get; set; } = ""; [Parameter] public EventCallback OnBack { get; set; } [Parameter] public EventCallback OnDeleted { get; set; } private string section = "environments"; private bool confirmDelete; // Environments private List? environments; private HashSet linkedEnvIds = new(); // Deployments private List? deployments; private List? clusters; private AppDeployment? selectedDeployment; private string deploymentSection = "manifests"; private bool confirmDeleteDeployment; // Create deployment form private bool showCreateDeployment; private string newDeployName = ""; private DeploymentType newDeployType = DeploymentType.Manual; private Guid newDeployEnvId; private Guid newDeployClusterId; private string newDeployNamespace = ""; private string newHelmRepoUrl = ""; private string newHelmChartName = ""; private string newHelmChartVersion = ""; private string? createDeployError; // Manifest management private List? manifests; private bool showAddManifest; private string newManifestKind = ""; private string newManifestName = ""; private string newManifestYaml = ""; private int newManifestSortOrder; private Guid? editingManifestId; private string editManifestYaml = ""; // Helm values private string helmValuesYaml = ""; // Resource tree private List? resourceTree; protected override async Task OnInitializedAsync() { await LoadEnvironments(); } // ──────── App ──────── private async Task DeleteApp() { await TenantService.DeleteAppAsync(App.Id); await OnDeleted.InvokeAsync(); } // ──────── Environments ──────── private async Task LoadEnvironments() { environments = await TenantService.GetEnvironmentsAsync(TenantId); // Refresh the app to get current environment links. List apps = await TenantService.GetAppsAsync(App.CustomerId); Data.App? freshApp = apps.FirstOrDefault(a => a.Id == App.Id); linkedEnvIds = freshApp?.AppEnvironments.Select(ae => ae.EnvironmentId).ToHashSet() ?? new(); } private async Task ToggleEnvironment(Guid envId, bool currentlyLinked) { if (currentlyLinked) { await TenantService.UnlinkAppFromEnvironmentAsync(App.Id, envId); } else { await TenantService.LinkAppToEnvironmentAsync(App.Id, envId); } await LoadEnvironments(); } // ──────── Deployments ──────── private async Task SwitchToDeployments() { section = "deployments"; if (deployments is null) { await LoadDeployments(); } if (clusters is null) { clusters = await TenantService.GetClustersAsync(TenantId); } } private async Task LoadDeployments() { deployments = await DeploymentService.GetDeploymentsAsync(App.Id); } private async Task CreateDeployment() { createDeployError = null; try { await DeploymentService.CreateDeploymentAsync( App.Id, newDeployName, newDeployType, newDeployEnvId, newDeployClusterId, newDeployNamespace, newDeployType == DeploymentType.HelmChart ? newHelmRepoUrl : null, newDeployType == DeploymentType.HelmChart ? newHelmChartName : null, newDeployType == DeploymentType.HelmChart ? newHelmChartVersion : null); // Reset form and reload. showCreateDeployment = false; newDeployName = ""; newDeployNamespace = ""; newHelmRepoUrl = ""; newHelmChartName = ""; newHelmChartVersion = ""; await LoadDeployments(); } catch (DbUpdateException) { createDeployError = $"A deployment named '{newDeployName}' already exists for this app."; } } private async Task SelectDeployment(AppDeployment deploy) { selectedDeployment = deploy; confirmDeleteDeployment = false; // Default to the appropriate sub-tab based on type. if (deploy.Type == DeploymentType.HelmChart) { deploymentSection = "helm-values"; helmValuesYaml = deploy.HelmValues ?? ""; } else { deploymentSection = "manifests"; await LoadManifests(); } } private async Task DeleteDeployment() { if (selectedDeployment is not null) { await DeploymentService.DeleteDeploymentAsync(selectedDeployment.Id); selectedDeployment = null; confirmDeleteDeployment = false; await LoadDeployments(); } } // ──────── Manifests ──────── private async Task LoadManifests() { if (selectedDeployment is not null) { manifests = await DeploymentService.GetManifestsAsync(selectedDeployment.Id); } } private async Task AddManifest() { if (selectedDeployment is null) { return; } await DeploymentService.AddManifestAsync( selectedDeployment.Id, newManifestKind, newManifestName, newManifestYaml, newManifestSortOrder); showAddManifest = false; newManifestKind = ""; newManifestName = ""; newManifestYaml = ""; newManifestSortOrder = 0; await LoadManifests(); } private void StartEditManifest(DeploymentManifest manifest) { editingManifestId = manifest.Id; editManifestYaml = manifest.YamlContent; } private async Task SaveManifest(Guid manifestId) { await DeploymentService.UpdateManifestAsync(manifestId, editManifestYaml); editingManifestId = null; await LoadManifests(); } private async Task DeleteManifest(Guid manifestId) { await DeploymentService.DeleteManifestAsync(manifestId); await LoadManifests(); } // ──────── Helm Values ──────── private async Task SaveHelmValues() { if (selectedDeployment is not null) { await DeploymentService.UpdateHelmValuesAsync(selectedDeployment.Id, helmValuesYaml); } } // ──────── Resources ──────── private async Task OpenResourcesTab() { deploymentSection = "resources"; if (selectedDeployment is not null) { resourceTree = await DeploymentService.GetResourceTreeAsync(selectedDeployment.Id); } } // ──────── Rendering helpers ──────── private RenderFragment GetTypeBadge(DeploymentType type) => type switch { DeploymentType.Manual => @Manual, DeploymentType.Yaml => @YAML, DeploymentType.HelmChart => @Helm, _ => @Unknown }; private RenderFragment GetSyncBadge(SyncStatus status) => status switch { SyncStatus.Synced => @Synced, SyncStatus.OutOfSync => @OutOfSync, SyncStatus.Syncing => @Syncing, SyncStatus.Failed => @Failed, _ => @Unknown }; private RenderFragment GetHealthBadge(HealthStatus status) => status switch { HealthStatus.Healthy => @Healthy, HealthStatus.Progressing => @Progressing, HealthStatus.Degraded => @Degraded, HealthStatus.Missing => @Missing, HealthStatus.Suspended => @Suspended, _ => @Unknown }; private RenderFragment RenderResource(DeploymentResource resource, int depth) => __builder => {
@resource.Kind @resource.Name
@GetSyncBadge(resource.SyncStatus) @GetHealthBadge(resource.HealthStatus)
@if (resource.ChildResources?.Count > 0) { @foreach (DeploymentResource child in resource.ChildResources) { @RenderResource(child, depth + 1) } } }; }