@using EntKube.Web.Data @using EntKube.Web.Services @inject KeycloakService KeycloakService @inject StorageService StorageService @inject ComponentLifecycleService LifecycleService @if (loading) {

Scanning for Keycloak instances...

} else if (selectedRealm is not null && selectedKeycloak is not null) { @* ── Level 3: Realm detail ── *@
@selectedRealm.DisplayName
@selectedRealm.RealmName
@if (selectedRealm.LinkedApp is not null) { @selectedRealm.LinkedApp.Customer.Name / @selectedRealm.LinkedApp.Name }
@* ── Users ── *@ @if (realmTab == "users") { @if (loadingUsers) {
} else {
@(users?.Count ?? 0) users
@if (showAddUser) {
@if (userError is not null) {
@userError
}
} @if (users is not null) {
@foreach (KeycloakUserInfo user in users) {
@user.Username @user.Email @if (!user.Enabled) { Disabled } @if (!user.EmailVerified) { Unverified } @if (user.Attributes.Count > 0) {
@foreach (KeyValuePair> attr in user.Attributes) { @attr.Key: @string.Join(", ", attr.Value) }
}
}
} } } @* ── Identity Providers ── *@ @if (realmTab == "idp") { @if (loadingIdps) {
} else {
@(idps?.Count ?? 0) providers
@if (showAddIdp) {
@if (idpError is not null) {
@idpError
}
} @if (idps is not null) {
@foreach (KeycloakIdpInfo idp in idps) {
@idp.Alias @idp.ProviderId @if (!idp.Enabled) { Disabled }
}
} } } @* ── Groups ── *@ @if (realmTab == "groups") { @if (loadingGroups) {
} else {
@(groups?.Count ?? 0) groups
@if (showAddGroup) {
@if (groupError is not null) {
@groupError
} } @if (groups is not null) {
@foreach (KeycloakGroupInfo g in groups) {
@g.Name @g.Path
}
} } } @* ── Organizations ── *@ @if (realmTab == "orgs") { @if (loadingOrgs) {
} else {
@(orgs?.Count ?? 0) organizations (Keycloak 26+)
@if (showAddOrg) {
@if (orgError is not null) {
@orgError
}
} @if (orgs is not null) {
@foreach (KeycloakOrganizationInfo org in orgs) {
@org.Name @if (!string.IsNullOrWhiteSpace(org.Description)) { @org.Description } @if (!org.Enabled) { Disabled }
}
} } } @* ── Themes ── *@ @if (realmTab == "themes") { @if (realmDetails is null) {
} else { @if (realmThemeError is not null) {
@realmThemeError
}
} } @* ── Backup & Restore ── *@ @if (realmTab == "backup") { @if (loadingBackups) {
} else { @if (backupError is not null) {
@backupError
}
Create Backup
@if (backups is not null && backups.Count > 0) {
Backups
@foreach (KeycloakBackup b in backups) {
@b.RealmName @b.CreatedAt.ToString("yyyy-MM-dd HH:mm") @if (b.Status == KeycloakBackupStatus.Ready) { Ready @FormatSize(b.SizeBytes) } else if (b.Status == KeycloakBackupStatus.Failed) { Failed @b.LastError } else { Creating }
@if (b.Status == KeycloakBackupStatus.Ready) {
}
}
} } } @* ── Customer Portal ── *@ @if (realmTab == "portal") { @if (portalError is not null) {
@portalError
} @if (selectedRealm.LinkedApp is not null) {
Linked to @selectedRealm.LinkedApp.Customer.Name / @selectedRealm.LinkedApp.Name
} else {

Link this realm to a customer app so the customer can manage users, groups, and IdPs through their portal.

} } } else if (selectedKeycloak is not null) { @* ── Level 2: Realm list for a specific Keycloak ── *@
@selectedKeycloak.Component.Name @selectedKeycloak.Component.Cluster.Name
@if (selectedKeycloak.Config?.AdminUrl is not null) { @selectedKeycloak.Config.AdminUrl }
@if (showEditConfig && selectedKeycloak.Config is not null) {
Keycloak Settings
@if (configError is not null) {
@configError
}
} @if (!selectedKeycloak.IsConfigured) { @* Setup form for unconfigured keycloak *@
Not Configured
@if (selectedKeycloak.Component.Status == ComponentStatus.Failed) {
The install failed — likely because the database credentials weren't available before the pod started. Configure the database here, then retry the install from the Cluster tab.
}

Enter the admin credentials for this Keycloak instance. Optionally link a managed CNPG database — if the instance already runs against its own Postgres, leave the database unselected. Credentials are synced to a Kubernetes Secret in the @(selectedKeycloak.Component.Namespace ?? "keycloak") namespace.

@if (setupError is not null) {
@setupError
}
@if (availableDatabases is not null && availableDatabases.Count == 0) {
No ready CNPG databases found on this cluster. Create one in the Databases tab first.
}
@if (!string.IsNullOrWhiteSpace(selectedKeycloak.SuggestedAdminUrl)) { }
} else { @* Instance-level tabs: Realms | Themes *@ @if (instanceTab == "themes") { @* ── Themes tab ── *@
@foreach (string type in new[] { "login", "account", "admin", "email" }) { }
@if (loadingThemes) {
Loading themes... } else { @if (availableThemes is not null && availableThemes.TryGetValue(themeType, out List? themeNames) && themeNames.Count > 0) {
Available themes
@foreach (string name in themeNames) { @name }
}
Stored in EntKube. Apply to realms individually via the realm's Themes tab.
@if (themeSaved) { Saved } @if (themeError is not null) { @themeError }
} } else { @* Realms list *@ @if (realmSyncError is not null) {
Could not reach Keycloak — showing cached realms.
@realmSyncError
}
@(realms?.Count ?? 0) realms
@if (showCreateRealm) {
@if (realmError is not null) {
@realmError
}
} @if (realms is not null) { @if (realms.Count == 0) {
No realms yet. Create one above.
} else {
@foreach (KeycloakRealm realm in realms) {
@realm.DisplayName @realm.RealmName @if (!realm.Enabled) { Disabled } @if (realm.LinkedApp is not null) { @realm.LinkedApp.Customer.Name / @realm.LinkedApp.Name }
}
} } } @* end realms tab else *@ } } else { @* ── Level 1: Keycloak instance list ── *@ @if (detectedKeycloaks is null || detectedKeycloaks.Count == 0) {

No Keycloak instances found on this tenant's clusters.

Install the Keycloak component from the Components tab on one of your clusters.
} else {

Detected @detectedKeycloaks.Count Keycloak @(detectedKeycloaks.Count == 1 ? "instance" : "instances"). Select one to manage realms, users, identity providers, and backups.

@foreach (DetectedKeycloak kc in detectedKeycloaks) {
@kc.Component.Name
@kc.Component.Cluster.Name
@if (kc.Component.Status == ComponentStatus.Failed) { Install failed } @if (kc.IsConfigured) { Configured } else { Setup needed }
@if (kc.Config?.AdminUrl is not null) {
@kc.Config.AdminUrl
} @if (kc.Config?.CnpgDatabase is not null) {
@kc.Config.CnpgDatabase.CnpgCluster.Name / @kc.Config.CnpgDatabase.Name
}
}
} } @code { [Parameter] public Guid TenantId { get; set; } // ── Load state ──────────────────────────────────────────────────────────── private bool loading = true; private List? detectedKeycloaks; private List? realms; private List? availableDatabases; private List? storageLinks; private List? apps; // ── Navigation ──────────────────────────────────────────────────────────── private DetectedKeycloak? selectedKeycloak; private KeycloakRealm? selectedRealm; private string realmTab = "users"; private string instanceTab = "realms"; // ── Themes tab ─────────────────────────────────────────────────────────── private string themeType = "login"; private Dictionary>? availableThemes; private bool loadingThemes; private string? themeCss; private bool savingTheme; private bool themeSaved; private string? themeError; // ── Per-tab lazy data ───────────────────────────────────────────────────── private List? users; private List? idps; private List? groups; private List? orgs; private List? backups; private KeycloakRealmDetails? realmDetails; private bool loadingUsers, loadingIdps, loadingGroups, loadingOrgs, loadingBackups; // ── Setup form ──────────────────────────────────────────────────────────── private bool configuringKeycloak; private string? setupError; private Guid setupDatabaseId; private string setupAdminUrl = ""; private string setupUsername = "admin"; private string setupPassword = ""; // ── Edit config form ────────────────────────────────────────────────────── private bool showEditConfig; private bool savingConfig; private string? configError; private string editAdminUrl = ""; private string editAdminUsername = "admin"; private string editAdminPassword = ""; // ── Realm form ──────────────────────────────────────────────────────────── private bool showCreateRealm; private bool creatingRealm; private string? realmError; private string? realmSyncError; private string newRealmName = ""; private string newRealmDisplayName = ""; private string newRealmTheme = ""; private Guid? deletingRealmId; // ── User form ───────────────────────────────────────────────────────────── private bool showAddUser; private bool savingUser; private string? userError; private string newUserUsername = ""; private string newUserEmail = ""; private string newUserFirstName = ""; private string newUserLastName = ""; private string newUserPassword = ""; // ── IdP form ────────────────────────────────────────────────────────────── private bool showAddIdp; private bool savingIdp; private string? idpError; private string newIdpAlias = ""; private string newIdpProviderId = "oidc"; private string newIdpClientId = ""; private string newIdpClientSecret = ""; // ── Group form ──────────────────────────────────────────────────────────── private bool showAddGroup; private bool savingGroup; private string? groupError; private string newGroupName = ""; // ── Org form ───────────────────────────────────────────────────────────── private bool showAddOrg; private bool savingOrg; private string? orgError; private string newOrgName = ""; private string newOrgDescription = ""; // ── Realm theme form ────────────────────────────────────────────────────── private bool savingThemes; private string? realmThemeError; private string editLoginTheme = ""; private string editAccountTheme = ""; // ── Backup form ─────────────────────────────────────────────────────────── private bool creatingBackup; private string? backupError; private Guid backupStorageLinkId; // ── Portal form ─────────────────────────────────────────────────────────── private string? portalError; private Guid linkAppId; protected override async Task OnInitializedAsync() { detectedKeycloaks = await KeycloakService.GetDetectedKeycloaksAsync(TenantId); loading = false; } private async Task SelectKeycloak(DetectedKeycloak kc) { selectedKeycloak = kc; realms = null; availableDatabases = null; setupError = null; setupDatabaseId = Guid.Empty; setupAdminUrl = kc.SuggestedAdminUrl ?? ""; setupPassword = ""; instanceTab = "realms"; availableThemes = null; themeCss = null; themeSaved = false; themeError = null; realmSyncError = null; if (kc.IsConfigured) { try { realms = await KeycloakService.SyncRealmsFromKeycloakAsync(TenantId, kc.Config!.Id); } catch (Exception ex) { realmSyncError = ex.Message; realms = await KeycloakService.GetRealmsForConfigAsync(TenantId, kc.Config!.Id); } storageLinks ??= await StorageService.GetStorageLinksAsync(TenantId); apps ??= await KeycloakService.GetAppsAsync(TenantId); } else { availableDatabases = await KeycloakService.GetDatabasesForComponentAsync( TenantId, kc.Component.Id); } if (kc.Config is not null) { editAdminUrl = kc.Config.AdminUrl ?? ""; editAdminUsername = kc.Config.AdminUsername; } } private async Task SwitchInstanceTab(string tab) { instanceTab = tab; themeSaved = false; themeError = null; if (tab == "themes" && selectedKeycloak?.Config is not null) { await LoadThemesTab(); } } private async Task SwitchThemeType(string type) { themeType = type; themeSaved = false; themeError = null; await LoadThemeCss(); } private async Task LoadThemesTab() { if (selectedKeycloak?.Config is null) return; loadingThemes = true; StateHasChanged(); try { availableThemes = await KeycloakService.GetAvailableThemesAsync(TenantId, selectedKeycloak.Config.Id); await LoadThemeCss(); } catch { availableThemes = []; } finally { loadingThemes = false; } } private async Task LoadThemeCss() { if (selectedKeycloak is null) return; themeCss = await KeycloakService.GetThemeCssAsync(TenantId, selectedKeycloak.Component.Id, themeType); } private async Task SaveThemeCss() { if (selectedKeycloak is null) return; savingTheme = true; themeSaved = false; themeError = null; try { await KeycloakService.SaveThemeCssAsync( TenantId, selectedKeycloak.Component.Id, themeType, themeCss ?? ""); themeSaved = true; } catch (Exception ex) { themeError = ex.Message; } finally { savingTheme = false; } } private void BackToList() { selectedKeycloak = null; selectedRealm = null; realms = null; } private async Task OpenRealm(KeycloakRealm realm) { selectedRealm = realm; realmTab = "users"; users = null; idps = null; groups = null; orgs = null; backups = null; realmDetails = null; await SwitchRealmTab("users"); } private void BackToRealms() { selectedRealm = null; users = null; idps = null; groups = null; orgs = null; backups = null; realmDetails = null; } private async Task SwitchRealmTab(string tab) { realmTab = tab; if (selectedRealm is null) return; switch (tab) { case "users" when users is null: loadingUsers = true; StateHasChanged(); try { users = await KeycloakService.GetUsersAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { userError = ex.Message; } finally { loadingUsers = false; } break; case "idp" when idps is null: loadingIdps = true; StateHasChanged(); try { idps = await KeycloakService.GetIdpsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { idpError = ex.Message; } finally { loadingIdps = false; } break; case "groups" when groups is null: loadingGroups = true; StateHasChanged(); try { groups = await KeycloakService.GetGroupsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { groupError = ex.Message; } finally { loadingGroups = false; } break; case "orgs" when orgs is null: loadingOrgs = true; StateHasChanged(); try { orgs = await KeycloakService.GetOrganizationsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { orgError = ex.Message; } finally { loadingOrgs = false; } break; case "themes" when realmDetails is null: StateHasChanged(); try { realmDetails = await KeycloakService.GetRealmDetailsAsync(TenantId, selectedRealm.Id); editLoginTheme = realmDetails.LoginTheme ?? ""; editAccountTheme = realmDetails.AccountTheme ?? ""; } catch (Exception ex) { realmThemeError = ex.Message; } break; case "backup" when backups is null: loadingBackups = true; storageLinks ??= await StorageService.GetStorageLinksAsync(TenantId); StateHasChanged(); try { backups = await KeycloakService.GetBackupsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { backupError = ex.Message; } finally { loadingBackups = false; } break; case "portal": apps ??= await KeycloakService.GetAppsAsync(TenantId); break; } } // ── Setup ───────────────────────────────────────────────────────────────── private void UseSuggestedUrl() { setupAdminUrl = selectedKeycloak?.SuggestedAdminUrl ?? ""; } private async Task ConfigureKeycloak() { if (selectedKeycloak is null) return; configuringKeycloak = true; setupError = null; try { KeycloakComponentConfig config = await KeycloakService.ConfigureAsync( TenantId, selectedKeycloak.Component.Id, setupDatabaseId == Guid.Empty ? null : setupDatabaseId, setupUsername, setupPassword, setupAdminUrl); // Sync the credentials to the K8s secret immediately so the pod can start. await LifecycleService.SyncComponentSecretsAsync(selectedKeycloak.Component.Id); // Import any realms that already exist in this Keycloak instance. await KeycloakService.SyncRealmsFromKeycloakAsync(TenantId, config.Id); // Refresh detection list and re-select the now-configured keycloak. detectedKeycloaks = await KeycloakService.GetDetectedKeycloaksAsync(TenantId); DetectedKeycloak? updated = detectedKeycloaks.FirstOrDefault( k => k.Component.Id == selectedKeycloak.Component.Id); if (updated is not null) { await SelectKeycloak(updated); } } catch (Exception ex) { setupError = ex.Message; } finally { configuringKeycloak = false; } } private async Task SaveConfig() { if (selectedKeycloak?.Config is null) return; savingConfig = true; configError = null; try { await KeycloakService.UpdateConfigAsync( TenantId, selectedKeycloak.Config.Id, editAdminUrl, editAdminUsername, string.IsNullOrWhiteSpace(editAdminPassword) ? null : editAdminPassword); showEditConfig = false; detectedKeycloaks = await KeycloakService.GetDetectedKeycloaksAsync(TenantId); DetectedKeycloak? updated = detectedKeycloaks.FirstOrDefault( k => k.Component.Id == selectedKeycloak.Component.Id); if (updated is not null) selectedKeycloak = updated; } catch (Exception ex) { configError = ex.Message; } finally { savingConfig = false; } } // ── Realm CRUD ──────────────────────────────────────────────────────────── private async Task CreateRealm() { if (selectedKeycloak?.Config is null) return; creatingRealm = true; realmError = null; try { await KeycloakService.CreateRealmAsync( TenantId, selectedKeycloak.Config.Id, newRealmName, newRealmDisplayName, string.IsNullOrWhiteSpace(newRealmTheme) ? null : newRealmTheme); realms = await KeycloakService.GetRealmsForConfigAsync(TenantId, selectedKeycloak.Config.Id); showCreateRealm = false; newRealmName = ""; newRealmDisplayName = ""; newRealmTheme = ""; } catch (Exception ex) { realmError = ex.Message; } finally { creatingRealm = false; } } private async Task DeleteRealm(Guid realmId) { if (selectedKeycloak?.Config is null) return; deletingRealmId = realmId; try { await KeycloakService.DeleteRealmAsync(TenantId, realmId); realms = await KeycloakService.GetRealmsForConfigAsync(TenantId, selectedKeycloak.Config.Id); } catch (Exception ex) { realmError = ex.Message; } finally { deletingRealmId = null; } } // ── User CRUD ───────────────────────────────────────────────────────────── private async Task AddUser() { if (selectedRealm is null) return; savingUser = true; userError = null; try { await KeycloakService.CreateUserAsync( TenantId, selectedRealm.Id, newUserUsername, newUserEmail, newUserFirstName, newUserLastName, string.IsNullOrWhiteSpace(newUserPassword) ? null : newUserPassword); users = await KeycloakService.GetUsersAsync(TenantId, selectedRealm.Id); showAddUser = false; newUserUsername = ""; newUserEmail = ""; newUserFirstName = ""; newUserLastName = ""; newUserPassword = ""; } catch (Exception ex) { userError = ex.Message; } finally { savingUser = false; } } private async Task DeleteUser(string userId) { if (selectedRealm is null) return; try { await KeycloakService.DeleteUserAsync(TenantId, selectedRealm.Id, userId); users = await KeycloakService.GetUsersAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { userError = ex.Message; } } // ── IdP CRUD ────────────────────────────────────────────────────────────── private async Task AddIdp() { if (selectedRealm is null) return; savingIdp = true; idpError = null; try { await KeycloakService.CreateIdpAsync( TenantId, selectedRealm.Id, newIdpAlias, newIdpProviderId, newIdpClientId, newIdpClientSecret); idps = await KeycloakService.GetIdpsAsync(TenantId, selectedRealm.Id); showAddIdp = false; newIdpAlias = ""; newIdpClientId = ""; newIdpClientSecret = ""; } catch (Exception ex) { idpError = ex.Message; } finally { savingIdp = false; } } private async Task DeleteIdp(string alias) { if (selectedRealm is null) return; try { await KeycloakService.DeleteIdpAsync(TenantId, selectedRealm.Id, alias); idps = await KeycloakService.GetIdpsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { idpError = ex.Message; } } // ── Group CRUD ──────────────────────────────────────────────────────────── private async Task AddGroup() { if (selectedRealm is null) return; savingGroup = true; groupError = null; try { await KeycloakService.CreateGroupAsync(TenantId, selectedRealm.Id, newGroupName); groups = await KeycloakService.GetGroupsAsync(TenantId, selectedRealm.Id); showAddGroup = false; newGroupName = ""; } catch (Exception ex) { groupError = ex.Message; } finally { savingGroup = false; } } private async Task DeleteGroup(string groupId) { if (selectedRealm is null) return; try { await KeycloakService.DeleteGroupAsync(TenantId, selectedRealm.Id, groupId); groups = await KeycloakService.GetGroupsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { groupError = ex.Message; } } // ── Org CRUD ────────────────────────────────────────────────────────────── private async Task AddOrg() { if (selectedRealm is null) return; savingOrg = true; orgError = null; try { await KeycloakService.CreateOrganizationAsync( TenantId, selectedRealm.Id, newOrgName, string.IsNullOrWhiteSpace(newOrgDescription) ? null : newOrgDescription); orgs = await KeycloakService.GetOrganizationsAsync(TenantId, selectedRealm.Id); showAddOrg = false; newOrgName = ""; newOrgDescription = ""; } catch (Exception ex) { orgError = ex.Message; } finally { savingOrg = false; } } // ── Themes ──────────────────────────────────────────────────────────────── private async Task SaveThemes() { if (selectedRealm is null) return; savingThemes = true; realmThemeError = null; try { await KeycloakService.UpdateRealmThemesAsync( TenantId, selectedRealm.Id, string.IsNullOrWhiteSpace(editLoginTheme) ? null : editLoginTheme, string.IsNullOrWhiteSpace(editAccountTheme) ? null : editAccountTheme); } catch (Exception ex) { realmThemeError = ex.Message; } finally { savingThemes = false; } } // ── Backup ──────────────────────────────────────────────────────────────── private async Task CreateBackup() { if (selectedRealm is null || backupStorageLinkId == Guid.Empty) return; creatingBackup = true; backupError = null; try { await KeycloakService.CreateBackupAsync(TenantId, selectedRealm.Id, backupStorageLinkId); backups = await KeycloakService.GetBackupsAsync(TenantId, selectedRealm.Id); } catch (Exception ex) { backupError = ex.Message; } finally { creatingBackup = false; } } private async Task RestoreBackup(Guid backupId) { backupError = null; try { await KeycloakService.RestoreBackupAsync(TenantId, backupId, null); } catch (Exception ex) { backupError = ex.Message; } } private async Task DeleteBackup(Guid backupId) { backupError = null; try { await KeycloakService.DeleteBackupAsync(TenantId, backupId); if (selectedRealm is not null) { backups = await KeycloakService.GetBackupsAsync(TenantId, selectedRealm.Id); } } catch (Exception ex) { backupError = ex.Message; } } // ── Portal ──────────────────────────────────────────────────────────────── private async Task LinkApp() { if (selectedRealm is null || linkAppId == Guid.Empty) return; portalError = null; try { await KeycloakService.LinkRealmToAppAsync(TenantId, selectedRealm.Id, linkAppId); if (selectedKeycloak?.Config is not null) { realms = await KeycloakService.GetRealmsForConfigAsync(TenantId, selectedKeycloak.Config.Id); selectedRealm = realms.FirstOrDefault(r => r.Id == selectedRealm.Id); } } catch (Exception ex) { portalError = ex.Message; } } private async Task UnlinkApp() { if (selectedRealm is null) return; portalError = null; try { await KeycloakService.UnlinkRealmFromAppAsync(TenantId, selectedRealm.Id); if (selectedKeycloak?.Config is not null) { realms = await KeycloakService.GetRealmsForConfigAsync(TenantId, selectedKeycloak.Config.Id); selectedRealm = realms.FirstOrDefault(r => r.Id == selectedRealm.Id); } } catch (Exception ex) { portalError = ex.Message; } } // ── Utilities ───────────────────────────────────────────────────────────── private static string FormatSize(long bytes) { if (bytes < 1024) return $"{bytes} B"; if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; return $"{bytes / (1024.0 * 1024):F1} MB"; } }