@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 ── *@
Back to Realms
@selectedRealm.DisplayName
@selectedRealm.RealmName
@if (selectedRealm.LinkedApp is not null)
{
@selectedRealm.LinkedApp.Customer.Name / @selectedRealm.LinkedApp.Name
}
@foreach (string tab in new[] { "users", "idp", "groups", "orgs", "themes", "backup", "portal" })
{
string label = tab switch
{
"users" => "Users",
"idp" => "Identity Providers",
"groups" => "Groups",
"orgs" => "Organizations",
"themes" => "Themes",
"backup" => "Backup & Restore",
"portal" => "Customer Portal",
_ => tab
};
SwitchRealmTab(tab)">@label
}
@* ── Users ── *@
@if (realmTab == "users")
{
@if (loadingUsers)
{
}
else
{
@(users?.Count ?? 0) users
showAddUser = !showAddUser">
Add User
@if (showAddUser)
{
@if (userError is not null)
{
@userError
}
@if (savingUser) { }
Create
showAddUser = false">Cancel
}
@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)
}
}
DeleteUser(user.Id)">
}
}
}
}
@* ── Identity Providers ── *@
@if (realmTab == "idp")
{
@if (loadingIdps)
{
}
else
{
@(idps?.Count ?? 0) providers
showAddIdp = !showAddIdp">
Add Provider
@if (showAddIdp)
{
@if (idpError is not null)
{
@idpError
}
@if (savingIdp) { }
Add
showAddIdp = false">Cancel
}
@if (idps is not null)
{
@foreach (KeycloakIdpInfo idp in idps)
{
@idp.Alias
@idp.ProviderId
@if (!idp.Enabled)
{
Disabled
}
DeleteIdp(idp.Alias)">
}
}
}
}
@* ── Groups ── *@
@if (realmTab == "groups")
{
@if (loadingGroups)
{
}
else
{
@(groups?.Count ?? 0) groups
showAddGroup = !showAddGroup">
Add Group
@if (showAddGroup)
{
@if (savingGroup) { }
Create
showAddGroup = false">Cancel
@if (groupError is not null)
{
@groupError
}
}
@if (groups is not null)
{
@foreach (KeycloakGroupInfo g in groups)
{
@g.Name @g.Path
DeleteGroup(g.Id)">
}
}
}
}
@* ── Organizations ── *@
@if (realmTab == "orgs")
{
@if (loadingOrgs)
{
}
else
{
@(orgs?.Count ?? 0) organizations (Keycloak 26+)
showAddOrg = !showAddOrg">
Add Organization
@if (showAddOrg)
{
@if (orgError is not null)
{
@orgError
}
@if (savingOrg) { }
Create
showAddOrg = false">Cancel
}
@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
}
Login Theme
(default)
@foreach (string t in realmDetails.Themes)
{
@t
}
Account Console Theme
(default)
@foreach (string t in realmDetails.Themes)
{
@t
}
@if (savingThemes) { }
Save
}
}
@* ── Backup & Restore ── *@
@if (realmTab == "backup")
{
@if (loadingBackups)
{
}
else
{
@if (backupError is not null)
{
@backupError
}
Create Backup
Storage Bucket
Select bucket...
@if (storageLinks is not null)
{
@foreach (StorageLink link in storageLinks)
{
@link.Name
}
}
@if (creatingBackup) { }
Backup Now
@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)
{
RestoreBackup(b.Id)">
Restore
DeleteBackup(b.Id)">
}
}
}
}
}
@* ── 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
Unlink
}
else
{
Link this realm to a customer app so the customer can manage users, groups, and IdPs through their portal.
App
Select app...
@if (apps is not null)
{
@foreach (Data.App app in apps)
{
@app.Customer.Name / @app.Name
}
}
Link
}
}
}
else if (selectedKeycloak is not null)
{
@* ── Level 2: Realm list for a specific Keycloak ── *@
All Keycloak Instances
@selectedKeycloak.Component.Name
@selectedKeycloak.Component.Cluster.Name
@if (selectedKeycloak.Config?.AdminUrl is not null)
{
@selectedKeycloak.Config.AdminUrl
}
showEditConfig = !showEditConfig">
Settings
@if (showEditConfig && selectedKeycloak.Config is not null)
{
@if (configError is not null)
{
@configError
}
@if (savingConfig) { }
Save
showEditConfig = false">Cancel
}
@if (!selectedKeycloak.IsConfigured)
{
@* Setup form for unconfigured keycloak *@
@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 (configuringKeycloak) { }
Configure
}
else
{
@* Instance-level tabs: Realms | Themes *@
SwitchInstanceTab("realms")'>
Realms
SwitchInstanceTab("themes")'>
Themes
@if (instanceTab == "themes")
{
@* ── Themes tab ── *@
@foreach (string type in new[] { "login", "account", "admin", "email" })
{
SwitchThemeType(type)">
@(char.ToUpper(type[0]) + type[1..])
}
@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
}
}
Custom CSS override
Stored in EntKube. Apply to realms individually via the realm's Themes tab.
@if (savingTheme) { }
Save
@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
showCreateRealm = !showCreateRealm">
New Realm
@if (showCreateRealm)
{
@if (realmError is not null)
{
@realmError
}
@if (creatingRealm) { }
Create
showCreateRealm = false">Cancel
}
@if (realms is not null)
{
@if (realms.Count == 0)
{
No realms yet. Create one above.
}
else
{
@foreach (KeycloakRealm realm in realms)
{
OpenRealm(realm)" style="cursor:pointer">
@realm.DisplayName
@realm.RealmName
@if (!realm.Enabled)
{
Disabled
}
@if (realm.LinkedApp is not null)
{
@realm.LinkedApp.Customer.Name / @realm.LinkedApp.Name
}
OpenRealm(realm)">
DeleteRealm(realm.Id)"
disabled="@(deletingRealmId == realm.Id)">
@if (deletingRealmId == realm.Id)
{
}
else
{
}
}
}
}
} @* 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)
{
}
}
}
@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";
}
}