This commit is contained in:
Nils Blomgren
2026-06-07 21:09:37 +02:00
parent 76b7e55931
commit ffe41b4413
41 changed files with 2927 additions and 460 deletions

View File

@@ -13,7 +13,6 @@
@inject GitRepositoryService GitRepoService
@inject GitSyncService GitSyncService
@inject IDbContextFactory<ApplicationDbContext> DbFactory
@inject IJSRuntime JS
@* ═══════════════════════════════════════════════════════════════════
App Detail — a full-page view for a single application.
@@ -394,13 +393,16 @@
<span>Apply to Cluster</span>
}
</button>
<button class="btn btn-sm btn-outline-primary" @onclick="() => showAddManifest = !showAddManifest">
<i class="bi bi-plus me-1"></i>Add Manifest
</button>
@if (!IsGitManaged)
{
<button class="btn btn-sm btn-outline-primary" @onclick="() => showAddManifest = !showAddManifest">
<i class="bi bi-plus me-1"></i>Add Manifest
</button>
}
</div>
</div>
<div class="card-body">
@if (showAddManifest)
@if (showAddManifest && !IsGitManaged)
{
<div class="card border-primary mb-3">
<div class="card-body">
@@ -421,9 +423,7 @@
</div>
<div class="mb-2">
<label class="form-label small">YAML Content</label>
<textarea id="new-manifest-yaml" class="form-control form-control-sm font-monospace" rows="8"
@bind="newManifestYaml"
placeholder="apiVersion: v1&#10;kind: Service&#10;..."></textarea>
<YamlEditor @bind-Value="newManifestYaml" />
</div>
<div class="d-flex gap-1">
<button class="btn btn-sm btn-primary" @onclick="AddManifest"
@@ -454,45 +454,47 @@
<span class="fw-medium">@manifest.Name</span>
<span class="text-muted small ms-2">#@manifest.SortOrder</span>
</div>
<div class="d-flex gap-1">
@if (editingManifestId == manifest.Id)
{
<button class="btn btn-sm btn-primary" @onclick="() => SaveManifest(manifest.Id)" disabled="@savingManifest">
@if (savingManifest) { <span class="spinner-border spinner-border-sm me-1"></span> }
Save
</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="() => confirmDeleteManifestId = manifest.Id">
<i class="bi bi-trash"></i>
</button>
}
</div>
@if (!IsGitManaged)
{
<div class="d-flex gap-1">
@if (editingManifestId == manifest.Id)
{
<button class="btn btn-sm btn-primary" @onclick="() => SaveManifest(manifest.Id)" disabled="@savingManifest">
@if (savingManifest) { <span class="spinner-border spinner-border-sm me-1"></span> }
Save
</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="() => confirmDeleteManifestId = manifest.Id">
<i class="bi bi-trash"></i>
</button>
}
</div>
}
</div>
@if (editingManifestId == manifest.Id)
{
<div class="card-body p-2">
<textarea id="edit-manifest-yaml-@manifest.Id" class="form-control form-control-sm font-monospace" rows="10"
@bind="editManifestYaml"></textarea>
<YamlEditor @bind-Value="editManifestYaml" />
</div>
}
else
{
<div class="card-body p-2">
<pre class="mb-0 small" style="max-height: 200px; overflow-y: auto;">@manifest.YamlContent</pre>
<div class="card-body p-0" style="border-radius: 0 0 var(--bs-card-border-radius) var(--bs-card-border-radius); overflow: hidden;">
<YamlEditor Value="@manifest.YamlContent" ReadOnly="true" Borderless="true" />
</div>
}
</div>
@@ -534,9 +536,7 @@
{
<div class="alert alert-danger py-1 small mb-2">@helmValuesError</div>
}
<textarea id="helm-values-editor" class="form-control font-monospace" rows="12"
@bind="helmValuesYaml"
placeholder="replicas: 3&#10;persistence:&#10; size: 10Gi"></textarea>
<YamlEditor @bind-Value="helmValuesYaml" />
<div class="d-flex align-items-center gap-2 mt-2 flex-wrap">
<button class="btn btn-sm btn-primary" @onclick="SaveHelmValues" disabled="@(savingHelmValues || installingHelm)">
@if (savingHelmValues) { <span class="spinner-border spinner-border-sm me-1"></span> }
@@ -594,7 +594,9 @@
Resources="@resourceTree"
Namespace="@selectedDeployment?.Namespace"
DeploymentId="@(selectedDeployment?.Id ?? Guid.Empty)"
AccessRole="@null" />
AccessRole="@null"
OnRefresh="OpenResourcesTab"
LastRefreshed="@resourceTreeRefreshedAt" />
</div>
</div>
}
@@ -1249,6 +1251,8 @@
private List<KubernetesCluster>? clusters;
private AppDeployment? selectedDeployment;
private string deploymentSection = "manifests";
private bool IsGitManaged => selectedDeployment?.Type is
DeploymentType.GitYaml or DeploymentType.GitHelm or DeploymentType.GitAppOfApps;
private bool confirmDeleteDeployment;
// Create deployment form
@@ -1311,12 +1315,10 @@
private bool? yamlApplySuccess;
private string? yamlApplyOutput;
// JS module for reading live textarea values
private IJSObjectReference? jsModule;
// Resource tree
private List<DeploymentResource>? resourceTree;
private string? resourcesError;
private DateTime? resourceTreeRefreshedAt;
// Metrics
private DeploymentMetricsSummary? deploymentMetrics;
@@ -1359,20 +1361,7 @@
await LoadEnvironments();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
jsModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./Components/Pages/Tenants/AppDetail.razor.js");
}
}
public async ValueTask DisposeAsync()
{
if (jsModule is not null)
await jsModule.DisposeAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
// ──────── App ────────
@@ -1609,9 +1598,6 @@
{
if (selectedDeployment is null) return;
if (jsModule is not null)
newManifestYaml = await jsModule.InvokeAsync<string>("getElementValue", "new-manifest-yaml");
savingManifest = true;
try
{
@@ -1635,9 +1621,6 @@
private async Task SaveManifest(Guid manifestId)
{
if (jsModule is not null)
editManifestYaml = await jsModule.InvokeAsync<string>("getElementValue", $"edit-manifest-yaml-{manifestId}");
savingManifest = true;
try
{
@@ -1701,11 +1684,6 @@
private async Task SaveHelmValues()
{
// Read the live DOM value so we always have what the user actually typed,
// regardless of whether @bind's onchange fired before this click.
if (jsModule is not null)
helmValuesYaml = await jsModule.InvokeAsync<string>("getElementValue", "helm-values-editor");
savingHelmValues = true;
helmValuesSaved = false;
helmValuesError = null;
@@ -1738,6 +1716,7 @@
if (result.IsSuccess)
{
resourceTree = result.Data ?? [];
resourceTreeRefreshedAt = DateTime.UtcNow;
// Derive live status from the resource tree and persist it so
// the header badges (and the deployment list card) show real values.