@using EntKube.Web.Data @using EntKube.Web.Services @using Microsoft.EntityFrameworkCore @inject TenantService TenantService @inject CustomerAccessService CustomerAccessService @* =========================================================================== Three-level drill-down: Customers ▸ Apps ▸ App Detail The user sees ONE level at a time — no nested panel clutter. =========================================================================== *@ @if (selectedApp is not null) { @* ───────────── Level 3: App Detail ───────────── *@ } else if (selectedCustomer is not null) { @* ───────────── Level 2: Apps for a customer ───────────── *@

@selectedCustomer.Name

Applications owned by this customer
@* --- Add App --- *@
@if (!string.IsNullOrEmpty(appError)) {
@appError
}
@* --- App cards --- *@ @if (apps is null) {
} else if (apps.Count == 0) {

No apps yet. Create one above.

} else {
@foreach (Data.App app in apps) {
@app.Name
@if (app.AppEnvironments.Any()) { @foreach (AppEnvironment ae in app.AppEnvironments) { @ae.Environment.Name } } else { No environments linked }
Created @app.CreatedAt.ToString("MMM d, yyyy")
}
} @* --- Portal Access Management --- *@
Portal Access
Grant users access to this customer's apps via the customer portal.
@* --- Grant access form --- *@
@if (!string.IsNullOrEmpty(accessError)) {
@accessError
} @if (!string.IsNullOrEmpty(accessSuccess)) {
@accessSuccess
} @* --- Current access list --- *@ @if (customerAccesses is null) {
} else if (customerAccesses.Count == 0) {

No users have portal access to this customer yet.

} else { @foreach (CustomerAccess access in customerAccesses) {
@access.User.Email @access.Role
} }
} else { @* ───────────── Level 1: Customer list ───────────── *@

Customers

End-clients or accounts served by this tenant. Click a customer to manage their apps.

@if (!string.IsNullOrEmpty(errorMessage)) { } @if (customers is null) {
Loading customers...
} else if (customers.Count == 0) {

No customers yet. Create one above to get started.

} else {
@foreach (Customer customer in customers) {
@if (confirmDeleteId == customer.Id) {
Delete @customer.Name and all apps?
} else {
@customer.Name
@(appCounts.GetValueOrDefault(customer.Id, 0)) app(s) · Created @customer.CreatedAt.ToString("MMM d, yyyy")
}
}
} } @code { [Parameter] public Guid TenantId { get; set; } // --- Level 1: Customer list --- private List? customers; private Dictionary appCounts = new(); private string newName = ""; private string? errorMessage; private Guid? confirmDeleteId; // --- Level 2: Selected customer → apps --- private Customer? selectedCustomer; private List? apps; private string newAppName = ""; private string? appError; // --- Level 3: Selected app → detail --- private Data.App? selectedApp; // --- Portal access management --- private List? customerAccesses; private string grantEmail = ""; private CustomerAccessRole grantRole = CustomerAccessRole.Viewer; private string? accessError; private string? accessSuccess; protected override async Task OnInitializedAsync() { await LoadCustomers(); } // ──────────────── Level 1 ──────────────── private async Task LoadCustomers() { customers = await TenantService.GetCustomersAsync(TenantId); // Pre-load app counts for the list view. appCounts = new Dictionary(); foreach (Customer customer in customers) { List customerApps = await TenantService.GetAppsAsync(customer.Id); appCounts[customer.Id] = customerApps.Count; } } private async Task Create() { errorMessage = null; try { await TenantService.CreateCustomerAsync(TenantId, newName.Trim()); newName = ""; await LoadCustomers(); } catch (DbUpdateException) { errorMessage = "A customer with that name already exists."; } } private async Task Delete(Guid id) { confirmDeleteId = null; await TenantService.DeleteCustomerAsync(id); await LoadCustomers(); } private async Task OpenCustomer(Customer customer) { selectedCustomer = customer; selectedApp = null; appError = null; newAppName = ""; accessError = null; accessSuccess = null; await LoadApps(); await LoadAccesses(); } private async Task BackToCustomers() { selectedCustomer = null; selectedApp = null; apps = null; await LoadCustomers(); } // ──────────────── Level 2 ──────────────── private async Task LoadApps() { apps = await TenantService.GetAppsAsync(selectedCustomer!.Id); } private async Task CreateApp() { appError = null; try { await TenantService.CreateAppAsync(selectedCustomer!.Id, newAppName.Trim()); newAppName = ""; await LoadApps(); } catch (DbUpdateException) { appError = "An app with that name already exists for this customer."; } } private void OpenApp(Data.App app) { selectedApp = app; } private async Task BackToApps() { selectedApp = null; await LoadApps(); } private async Task AppDeleted() { selectedApp = null; await LoadApps(); } // ──────────────── Portal Access ──────────────── private async Task LoadAccesses() { customerAccesses = await CustomerAccessService.GetCustomerUsersAsync(selectedCustomer!.Id); } private async Task GrantAccess() { accessError = null; accessSuccess = null; // Look up the user by email. ApplicationUser? user = await TenantService.FindUserByEmailAsync(grantEmail.Trim()); if (user is null) { accessError = $"No user found with email '{grantEmail}'."; return; } try { await CustomerAccessService.GrantAccessAsync(user.Id, selectedCustomer!.Id, grantRole); accessSuccess = $"Access granted to {grantEmail}."; grantEmail = ""; await LoadAccesses(); } catch (Exception) { accessError = $"User '{grantEmail}' already has access to this customer."; } } private async Task RevokeAccess(string userId) { await CustomerAccessService.RevokeAccessAsync(userId, selectedCustomer!.Id); await LoadAccesses(); } private static string GetRoleBadgeClass(CustomerAccessRole role) => role switch { CustomerAccessRole.Admin => "bg-danger", CustomerAccessRole.Operator => "bg-warning text-dark", CustomerAccessRole.Viewer => "bg-info", _ => "bg-secondary" }; }