@using EntKube.Web.Data @using EntKube.Web.Services @inject RabbitMQService RabbitMQService @inject StorageService StorageService @inject TenantService TenantService @* =========================================================================== Messaging Tab — RabbitMQ cluster management and topology. Full lifecycle for clusters plus topology management via the K8s Topology Operator (vhosts, queues, exchanges, routing bindings, users, permissions). App linkage syncs AMQP connection details into app namespace K8s Secrets. =========================================================================== *@ @if (loading) {

Loading RabbitMQ clusters...

} else { @* ── Operator availability badges ── *@
@if (clusterOperatorAvailable) { RabbitMQ Cluster Operator @if (operatorStatus?.ClusterOperatorClusterName is not null) { on @operatorStatus.ClusterOperatorClusterName } } else { RabbitMQ Cluster Operator — not installed } @if (topologyOperatorAvailable) { Topology Operator @if (operatorStatus?.TopologyOperatorClusterName is not null) { on @operatorStatus.TopologyOperatorClusterName } } else { Topology Operator — not installed }
@* ── Cluster list ── *@
RabbitMQ Clusters @clusters.Count
@if (showCreateForm) {
New RabbitMQ Cluster
DNS-safe, max 63 chars.
@if (!string.IsNullOrEmpty(createError)) {
@createError
}
} @if (clusters.Count == 0) {
No RabbitMQ clusters provisioned for this tenant.
} else { @foreach (RabbitMQCluster cluster in clusters) { bool expanded = expandedClusterId == cluster.Id;
@cluster.Name @cluster.Namespace @cluster.Status @cluster.KubernetesCluster.Name @cluster.KubernetesCluster.Environment.Name v@cluster.RabbitMQVersion • @cluster.Replicas nodes • @cluster.StorageSize @if (cluster.StorageLinkId.HasValue) { Backups }
@if (!string.IsNullOrEmpty(cluster.LastError) && cluster.Status == RabbitMQClusterStatus.Failed) {
@cluster.LastError
} @if (expanded) {
@if (!string.IsNullOrEmpty(topologyError)) {
@topologyError
} @* ── Vhosts ── *@ @if (clusterTab == "vhosts") {
Virtual Hosts
@if (showVhostForm) {
Use "/" for the default vhost or "/path" for a named vhost.
} @if (loadingTopology) {
} else if (vhosts.Count == 0) {

No vhosts managed via topology operator yet.

} else { @foreach (RabbitMQVhostInfo v in vhosts) { }
VhostK8s Name
@v.VhostName @v.K8sName
} } @* ── Queues ── *@ @if (clusterTab == "queues") {
Queues
@if (showQueueForm) {
} @if (loadingTopology) {
} else if (queues.Count == 0) {

No queues yet.

} else { @foreach (RabbitMQQueueInfo q in queues) { }
NameVhostTypeDurable
@q.QueueName @q.Vhost @q.QueueType @(q.Durable ? "✓" : "—")
} } @* ── Exchanges ── *@ @if (clusterTab == "exchanges") {
Exchanges
@if (showExchangeForm) {
} @if (loadingTopology) {
} else if (exchanges.Count == 0) {

No exchanges yet.

} else { @foreach (RabbitMQExchangeInfo ex in exchanges) { }
NameVhostTypeDurable
@ex.ExchangeName @ex.Vhost @ex.ExchangeType @(ex.Durable ? "✓" : "—")
} } @* ── Routing Bindings ── *@ @if (clusterTab == "routing") {
Routing Bindings
@if (showRoutingForm) {
} @if (loadingTopology) {
} else if (routingBindings.Count == 0) {

No routing bindings yet.

} else { @foreach (RabbitMQRoutingBindingInfo rb in routingBindings) { }
SourceDestinationTypeRouting KeyVhost
@rb.Source @rb.Destination @rb.DestinationType @rb.RoutingKey @rb.Vhost
} } @* ── Users ── *@ @if (clusterTab == "users") {
Users
@if (showUserForm) {
management, monitoring, administrator, policymaker
} @if (loadingTopology) {
} else if (users.Count == 0) {

No topology-managed users yet.

} else { @foreach (RabbitMQUserInfo u in users) { }
UsernameTagsK8s Name
@u.Username @foreach (string tag in u.Tags) { @tag } @u.K8sName
} @if (showPermissionForm && permissionTargetUser is not null) {
Set Permissions — @permissionTargetUser
} } @* ── App Links ── *@ @if (clusterTab == "app-links") {
App Links

Each link provisions a dedicated RabbitMQ user for the app and syncs the connection details (host, port, vhost, credentials, queue/exchange) into a Kubernetes Secret in the app's namespace.

@if (showAppLinkForm) {
A dedicated RabbitMQ user with full access to the chosen vhost will be automatically created and its credentials stored in the vault, then synced into the K8s secret below.
} @if (loadingTopology) {
} else if (messagingBindings.Count == 0) {

No app links yet.

} else { @foreach (MessagingBinding mb in messagingBindings) { }
App / DeploymentVhostQueueExchangeSecretLast Synced
@mb.AppDeployment.App.Customer.Name / @mb.AppDeployment.App.Name
@mb.AppDeployment.Name (@mb.AppDeployment.Cluster.Name)
@mb.Vhost @(mb.QueueName ?? "—") @(mb.ExchangeName ?? "—") @mb.KubernetesSecretName @if (mb.LastSyncedAt.HasValue) { @mb.LastSyncedAt.Value.ToLocalTime().ToString("MM-dd HH:mm") } else { Not synced }
} } @* ── Backup & Restore ── *@ @if (clusterTab == "backup") { @if (!cluster.StorageLinkId.HasValue) {
No S3 storage link is attached to this cluster. Edit the cluster to add one before creating backups.
} else {
Definitions exported via rabbitmqctl export_definitions and stored as JSON in S3.
} @if (!string.IsNullOrEmpty(backupError)) {
@backupError
} @if (showRestoreForm && restoreBackupId.HasValue) {
Restore Definitions
} @if (loadingBackups) {
} else if (clusterBackups.Count == 0) {

No backups yet.

} else { @foreach (RabbitMQBackup backup in clusterBackups) { }
CreatedSizeStatus
@backup.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm") @FormatBytes(backup.SizeBytes) @backup.Status @if (backup.Status == RabbitMQBackupStatus.Ready) { }
} } @* ── Credentials ── *@ @if (clusterTab == "credentials") {

Admin credentials generated by the Cluster Operator, stored encrypted in the tenant vault. Source: K8s Secret @(cluster.Name)-default-user in @cluster.Namespace.

@if (loadingCredentials) {
} else if (adminUsername is not null) {
Stored in vault } else {
Credentials not yet in vault. Click Sync from K8s once the cluster is running.
} @if (!string.IsNullOrEmpty(credentialError)) {
@credentialError
}

} @if (clusterTab == "metrics") { }
}
} }
@* ── Delete confirmation modal ── *@ @if (deleteTarget is not null) { } } @code { [Parameter] public Guid TenantId { get; set; } // ── State ────────────────────────────────────────────────────────────────── private bool loading = true; private List clusters = []; private List allK8sClusters = []; private List storageLinks = []; private List allDeployments = []; private bool clusterOperatorAvailable; private bool topologyOperatorAvailable; private RabbitMQOperatorStatus? operatorStatus; // Create cluster form private bool showCreateForm; private bool creating; private string createName = ""; private string createNamespace = "rabbitmq"; private string createVersion = "3.13"; private int createReplicas = 3; private string createStorageSize = "10Gi"; private string createStorageClass = ""; private Guid createK8sClusterId = Guid.Empty; private Guid createStorageLinkId = Guid.Empty; private string? createError; // Cluster detail private Guid? expandedClusterId; private string clusterTab = "vhosts"; private bool loadingTopology; private string? topologyError; private bool submitting; // Topology state private List vhosts = []; private List queues = []; private List exchanges = []; private List routingBindings = []; private List users = []; private List messagingBindings = []; // Vhost form private bool showVhostForm; private string newVhostName = "/"; // Queue form private bool showQueueForm; private string newQueueName = ""; private string newQueueVhost = "/"; private string newQueueType = "quorum"; private bool newQueueDurable = true; // Exchange form private bool showExchangeForm; private string newExchangeName = ""; private string newExchangeVhost = "/"; private string newExchangeType = "direct"; private bool newExchangeDurable = true; // Routing binding form private bool showRoutingForm; private string newBindingVhost = "/"; private string newBindingSource = ""; private string newBindingDest = ""; private string newBindingDestType = "queue"; private string newBindingRoutingKey = ""; // User form private bool showUserForm; private string newUsername = ""; private string newUserPassword = ""; private string newUserTags = ""; // Permission form private bool showPermissionForm; private string? permissionTargetUser; private string permVhost = "/"; private string permConfigure = ".*"; private string permWrite = ".*"; private string permRead = ".*"; // App link form private bool showAppLinkForm; private Guid linkAppDeploymentId = Guid.Empty; private string linkVhost = "/"; private string linkQueue = ""; private string linkExchange = ""; private string linkSecretName = "rabbitmq"; private Guid? syncingLinkId; // Backup private bool loadingBackups; private List clusterBackups = []; private Guid? backingUp; private string? backupError; private bool showRestoreForm; private Guid? restoreBackupId; private Guid restoreTargetClusterId; private bool restoring; // Credentials private bool loadingCredentials; private bool syncingCredentials; private string? adminUsername; private string? adminPassword; private bool showPassword; private string? credentialError; // Delete cluster private RabbitMQCluster? deleteTarget; private bool deleting; // ── Lifecycle ────────────────────────────────────────────────────────────── protected override async Task OnInitializedAsync() => await LoadAsync(); private async Task LoadAsync() { loading = true; Task> clustersTask = RabbitMQService.GetClustersAsync(TenantId); Task> k8sTask = TenantService.GetClustersAsync(TenantId); Task> storageTask = StorageService.GetStorageLinksAsync(TenantId); Task operatorTask = RabbitMQService.GetOperatorStatusAsync(TenantId); Task> deploymentsTask = RabbitMQService.GetTenantDeploymentsAsync(TenantId); await Task.WhenAll(clustersTask, k8sTask, storageTask, operatorTask, deploymentsTask); clusters = clustersTask.Result; allK8sClusters = k8sTask.Result; storageLinks = storageTask.Result; allDeployments = deploymentsTask.Result; operatorStatus = operatorTask.Result; clusterOperatorAvailable = operatorStatus.ClusterOperatorAvailable; topologyOperatorAvailable = operatorStatus.TopologyOperatorAvailable; loading = false; } // ── Cluster create / delete ──────────────────────────────────────────────── private void ShowCreateForm() { showCreateForm = true; createError = null; } private void CancelCreate() { showCreateForm = false; createError = null; } private async Task CreateCluster() { createError = null; if (string.IsNullOrWhiteSpace(createName)) { createError = "Name is required."; return; } if (createK8sClusterId == Guid.Empty) { createError = "Select a Kubernetes cluster."; return; } if (string.IsNullOrWhiteSpace(createStorageSize)) { createError = "Storage size is required."; return; } creating = true; try { await RabbitMQService.CreateClusterAsync( TenantId, createK8sClusterId, createName.Trim().ToLowerInvariant(), string.IsNullOrWhiteSpace(createNamespace) ? "rabbitmq" : createNamespace.Trim(), string.IsNullOrWhiteSpace(createVersion) ? "3.13" : createVersion.Trim(), createReplicas, createStorageSize.Trim(), string.IsNullOrWhiteSpace(createStorageClass) ? null : createStorageClass.Trim(), createStorageLinkId == Guid.Empty ? null : createStorageLinkId); showCreateForm = false; createName = ""; await LoadAsync(); } catch (Exception ex) { createError = ex.Message; } finally { creating = false; } } private void ConfirmDeleteCluster(RabbitMQCluster cluster) => deleteTarget = cluster; private async Task DeleteCluster() { if (deleteTarget is null) return; deleting = true; try { await RabbitMQService.DeleteClusterAsync(TenantId, deleteTarget.Id); expandedClusterId = null; deleteTarget = null; await LoadAsync(); } catch (Exception ex) { createError = ex.Message; } finally { deleting = false; } } // ── Cluster expand / tab switch ──────────────────────────────────────────── private async Task ToggleCluster(Guid clusterId) { if (expandedClusterId == clusterId) { expandedClusterId = null; return; } expandedClusterId = clusterId; clusterTab = "vhosts"; await LoadTopologyTab(clusterId); } private async Task SetClusterTab(string tab, Guid clusterId) { clusterTab = tab; topologyError = null; showVhostForm = showQueueForm = showExchangeForm = showRoutingForm = showUserForm = showPermissionForm = showAppLinkForm = false; switch (tab) { case "vhosts": case "queues": case "exchanges": case "routing": case "users": await LoadTopologyTab(clusterId); break; case "app-links": await LoadAppLinks(clusterId); break; case "backup": await LoadBackupsAsync(clusters.First(c => c.Id == clusterId)); break; case "credentials": await LoadCredentials(clusters.First(c => c.Id == clusterId)); break; } } private async Task LoadTopologyTab(Guid clusterId) { loadingTopology = true; topologyError = null; vhosts = []; queues = []; exchanges = []; routingBindings = []; users = []; try { switch (clusterTab) { case "vhosts": vhosts = await RabbitMQService.GetVhostsAsync(TenantId, clusterId); break; case "queues": queues = await RabbitMQService.GetQueuesAsync(TenantId, clusterId); break; case "exchanges": exchanges = await RabbitMQService.GetExchangesAsync(TenantId, clusterId); break; case "routing": routingBindings = await RabbitMQService.GetRoutingBindingsAsync(TenantId, clusterId); break; case "users": users = await RabbitMQService.GetUsersAsync(TenantId, clusterId); break; } } catch (Exception ex) { topologyError = ex.Message; } finally { loadingTopology = false; } } // ── Vhosts ──────────────────────────────────────────────────────────────── private async Task CreateVhost(RabbitMQCluster cluster) { if (string.IsNullOrWhiteSpace(newVhostName)) return; submitting = true; topologyError = null; try { await RabbitMQService.CreateVhostAsync(TenantId, cluster.Id, newVhostName.Trim()); showVhostForm = false; newVhostName = "/"; vhosts = await RabbitMQService.GetVhostsAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } private async Task DeleteVhost(RabbitMQCluster cluster, string k8sName) { topologyError = null; try { await RabbitMQService.DeleteVhostAsync(TenantId, cluster.Id, k8sName); vhosts = await RabbitMQService.GetVhostsAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } } // ── Queues ──────────────────────────────────────────────────────────────── private async Task CreateQueue(RabbitMQCluster cluster) { if (string.IsNullOrWhiteSpace(newQueueName)) return; submitting = true; topologyError = null; try { await RabbitMQService.CreateQueueAsync(TenantId, cluster.Id, newQueueVhost, newQueueName.Trim(), newQueueType, newQueueDurable, false); showQueueForm = false; newQueueName = ""; queues = await RabbitMQService.GetQueuesAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } private async Task DeleteQueue(RabbitMQCluster cluster, string k8sName) { topologyError = null; try { await RabbitMQService.DeleteQueueAsync(TenantId, cluster.Id, k8sName); queues = await RabbitMQService.GetQueuesAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } } // ── Exchanges ───────────────────────────────────────────────────────────── private async Task CreateExchange(RabbitMQCluster cluster) { if (string.IsNullOrWhiteSpace(newExchangeName)) return; submitting = true; topologyError = null; try { await RabbitMQService.CreateExchangeAsync(TenantId, cluster.Id, newExchangeVhost, newExchangeName.Trim(), newExchangeType, newExchangeDurable, false); showExchangeForm = false; newExchangeName = ""; exchanges = await RabbitMQService.GetExchangesAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } private async Task DeleteExchange(RabbitMQCluster cluster, string k8sName) { topologyError = null; try { await RabbitMQService.DeleteExchangeAsync(TenantId, cluster.Id, k8sName); exchanges = await RabbitMQService.GetExchangesAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } } // ── Routing bindings ────────────────────────────────────────────────────── private async Task CreateRoutingBinding(RabbitMQCluster cluster) { if (string.IsNullOrWhiteSpace(newBindingSource) || string.IsNullOrWhiteSpace(newBindingDest)) return; submitting = true; topologyError = null; try { await RabbitMQService.CreateRoutingBindingAsync(TenantId, cluster.Id, newBindingVhost, newBindingSource, newBindingDest, newBindingDestType, newBindingRoutingKey); showRoutingForm = false; newBindingSource = ""; newBindingDest = ""; newBindingRoutingKey = ""; routingBindings = await RabbitMQService.GetRoutingBindingsAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } private async Task DeleteRoutingBinding(RabbitMQCluster cluster, string k8sName) { topologyError = null; try { await RabbitMQService.DeleteRoutingBindingAsync(TenantId, cluster.Id, k8sName); routingBindings = await RabbitMQService.GetRoutingBindingsAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } } // ── Users ───────────────────────────────────────────────────────────────── private async Task CreateUser(RabbitMQCluster cluster) { if (string.IsNullOrWhiteSpace(newUsername) || string.IsNullOrWhiteSpace(newUserPassword)) return; submitting = true; topologyError = null; try { IEnumerable tags = newUserTags .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); await RabbitMQService.CreateUserAsync(TenantId, cluster.Id, newUsername.Trim(), newUserPassword, tags); showUserForm = false; newUsername = ""; newUserPassword = ""; newUserTags = ""; users = await RabbitMQService.GetUsersAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } private async Task DeleteUser(RabbitMQCluster cluster, string k8sName) { topologyError = null; try { await RabbitMQService.DeleteUserAsync(TenantId, cluster.Id, k8sName); users = await RabbitMQService.GetUsersAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } } private void PreparePermission(string username) { permissionTargetUser = username; permVhost = "/"; permConfigure = ".*"; permWrite = ".*"; permRead = ".*"; showPermissionForm = true; } private async Task SetPermission(RabbitMQCluster cluster) { if (permissionTargetUser is null) return; submitting = true; topologyError = null; try { await RabbitMQService.SetPermissionAsync(TenantId, cluster.Id, permVhost, permissionTargetUser, permConfigure, permWrite, permRead); showPermissionForm = false; } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } // ── App links ───────────────────────────────────────────────────────────── private void PrepareAppLink(RabbitMQCluster cluster, string? queueName, string? exchangeName, string vhost) { clusterTab = "app-links"; showAppLinkForm = true; linkVhost = vhost; linkQueue = queueName ?? ""; linkExchange = exchangeName ?? ""; linkSecretName = "rabbitmq"; _ = LoadAppLinks(cluster.Id); } private async Task LoadAppLinks(Guid clusterId) { loadingTopology = true; try { messagingBindings = await RabbitMQService.GetMessagingBindingsAsync(TenantId, clusterId); } catch (Exception ex) { topologyError = ex.Message; } finally { loadingTopology = false; } } private async Task CreateAppLink(RabbitMQCluster cluster) { if (linkAppDeploymentId == Guid.Empty || string.IsNullOrWhiteSpace(linkSecretName)) return; submitting = true; topologyError = null; try { await RabbitMQService.CreateMessagingBindingAsync( TenantId, cluster.Id, linkAppDeploymentId, linkVhost, string.IsNullOrWhiteSpace(linkQueue) ? null : linkQueue.Trim(), string.IsNullOrWhiteSpace(linkExchange) ? null : linkExchange.Trim(), linkSecretName.Trim()); showAppLinkForm = false; linkAppDeploymentId = Guid.Empty; linkQueue = ""; linkExchange = ""; linkSecretName = "rabbitmq"; messagingBindings = await RabbitMQService.GetMessagingBindingsAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } finally { submitting = false; } } private async Task SyncAppLink(MessagingBinding mb) { syncingLinkId = mb.Id; topologyError = null; try { await RabbitMQService.SyncMessagingBindingAsync(TenantId, mb.Id); messagingBindings = await RabbitMQService.GetMessagingBindingsAsync(TenantId, mb.RabbitMQClusterId); } catch (Exception ex) { topologyError = ex.Message; } finally { syncingLinkId = null; } } private async Task DeleteAppLink(RabbitMQCluster cluster, MessagingBinding mb) { topologyError = null; try { await RabbitMQService.DeleteMessagingBindingAsync(TenantId, mb.Id); messagingBindings = await RabbitMQService.GetMessagingBindingsAsync(TenantId, cluster.Id); } catch (Exception ex) { topologyError = ex.Message; } } // ── Backups ──────────────────────────────────────────────────────────────── private async Task LoadBackupsAsync(RabbitMQCluster cluster) { loadingBackups = true; backupError = null; try { clusterBackups = await RabbitMQService.GetBackupsAsync(TenantId, cluster.Id); } catch (Exception ex) { backupError = ex.Message; } finally { loadingBackups = false; } } private async Task TriggerBackup(RabbitMQCluster cluster) { backupError = null; backingUp = cluster.Id; try { if (!cluster.StorageLinkId.HasValue) { backupError = "No storage link attached."; return; } await RabbitMQService.CreateBackupAsync(TenantId, cluster.Id, cluster.StorageLinkId.Value); await LoadBackupsAsync(cluster); } catch (Exception ex) { backupError = ex.Message; } finally { backingUp = null; } } private void PrepareRestore(RabbitMQBackup backup) { restoreBackupId = backup.Id; restoreTargetClusterId = expandedClusterId!.Value; showRestoreForm = true; } private async Task ExecuteRestore() { if (!restoreBackupId.HasValue) return; restoring = true; backupError = null; try { await RabbitMQService.RestoreBackupAsync(TenantId, restoreBackupId.Value, restoreTargetClusterId); showRestoreForm = false; restoreBackupId = null; } catch (Exception ex) { backupError = ex.Message; } finally { restoring = false; } } private async Task DeleteBackup(RabbitMQBackup backup) { try { await RabbitMQService.DeleteBackupAsync(TenantId, backup.Id); RabbitMQCluster? cluster = clusters.FirstOrDefault(c => c.Id == backup.RabbitMQClusterId); if (cluster is not null) await LoadBackupsAsync(cluster); } catch (Exception ex) { backupError = ex.Message; } } // ── Credentials ──────────────────────────────────────────────────────────── private async Task LoadCredentials(RabbitMQCluster cluster) { loadingCredentials = true; adminUsername = null; adminPassword = null; credentialError = null; showPassword = false; try { (string Username, string Password)? creds = await RabbitMQService.GetVaultCredentialsAsync(TenantId, cluster.Id); if (creds.HasValue) { adminUsername = creds.Value.Username; adminPassword = creds.Value.Password; } } catch (Exception ex) { credentialError = ex.Message; } finally { loadingCredentials = false; } } private async Task SyncCredentials(RabbitMQCluster cluster) { syncingCredentials = true; credentialError = null; try { await RabbitMQService.SyncCredentialsToVaultAsync(TenantId, cluster.Id); await LoadCredentials(cluster); } catch (Exception ex) { credentialError = ex.Message; } finally { syncingCredentials = false; } } // ── Helpers ──────────────────────────────────────────────────────────────── private static string TabLabel(string tab) => tab switch { "vhosts" => "Vhosts", "queues" => "Queues", "exchanges" => "Exchanges", "routing" => "Routing", "users" => "Users", "app-links" => "App Links", "backup" => "Backup & Restore", "credentials" => "Credentials", "metrics" => "Metrics", _ => tab }; private static string StatusBadgeClass(RabbitMQClusterStatus status) => status switch { RabbitMQClusterStatus.Running => "bg-success-subtle text-success border border-success-subtle", RabbitMQClusterStatus.Creating => "bg-primary-subtle text-primary border border-primary-subtle", RabbitMQClusterStatus.Failed => "bg-danger-subtle text-danger border border-danger-subtle", RabbitMQClusterStatus.Deleting => "bg-warning-subtle text-warning border border-warning-subtle", _ => "bg-secondary-subtle text-secondary border" }; private static string BackupBadgeClass(RabbitMQBackupStatus status) => status switch { RabbitMQBackupStatus.Ready => "bg-success-subtle text-success border border-success-subtle", RabbitMQBackupStatus.Creating => "bg-primary-subtle text-primary border border-primary-subtle", RabbitMQBackupStatus.Failed => "bg-danger-subtle text-danger border border-danger-subtle", _ => "bg-secondary-subtle text-secondary border" }; private static string FormatBytes(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"; } }